diff options
390 files changed, 8202 insertions, 3428 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index 2bd5aee0cd24..0fe34fb650eb 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -311,6 +311,15 @@ java_defaults { compile_dex: true, } +java_defaults { + name: "android_stubs_dists_default", + dist: { + targets: ["sdk", "win_sdk"], + tag: ".jar", + dest: "android.jar", + }, +} + java_library_static { name: "android_monolith_stubs_current", srcs: [ ":api-stubs-docs" ], @@ -346,7 +355,21 @@ java_library_static { name: "android_system_monolith_stubs_current", srcs: [ ":system-api-stubs-docs" ], static_libs: [ "private-stub-annotations-jar" ], - defaults: ["android_defaults_stubs_current"], + defaults: [ + "android_defaults_stubs_current", + "android_stubs_dists_default", + ], + dist: { + dir: "apistubs/android/system", + }, + dists: [ + { + // Legacy dist path + targets: ["sdk", "win_sdk"], + tag: ".jar", + dest: "android_system.jar", + }, + ], } java_library_static { @@ -378,14 +401,34 @@ java_library_static { name: "android_test_stubs_current", srcs: [ ":test-api-stubs-docs" ], static_libs: [ "private-stub-annotations-jar" ], - defaults: ["android_defaults_stubs_current"], + defaults: [ + "android_defaults_stubs_current", + "android_stubs_dists_default", + ], + dist: { + dir: "apistubs/android/test", + }, + dists: [ + { + // Legacy dist path + targets: ["sdk", "win_sdk"], + tag: ".jar", + dest: "android_test.jar", + }, + ], } java_library_static { name: "android_module_lib_stubs_current", srcs: [ ":module-lib-api-stubs-docs-non-updatable" ], - defaults: ["android_defaults_stubs_current"], + defaults: [ + "android_defaults_stubs_current", + "android_stubs_dists_default", + ], libs: ["sdk_system_29_android"], + dist: { + dir: "apistubs/android/module-lib", + }, } java_library_static { diff --git a/apct-tests/perftests/windowmanager/Android.bp b/apct-tests/perftests/windowmanager/Android.bp index f02cbcfc4daf..9e95a104af81 100644 --- a/apct-tests/perftests/windowmanager/Android.bp +++ b/apct-tests/perftests/windowmanager/Android.bp @@ -19,6 +19,7 @@ android_test { "androidx.test.rules", "androidx.annotation_annotation", "apct-perftests-utils", + "platform-test-annotations", ], test_suites: ["device-tests"], platform_apis: true, diff --git a/apct-tests/perftests/windowmanager/AndroidTest.xml b/apct-tests/perftests/windowmanager/AndroidTest.xml index 69d187f9419a..0a80cf96fba3 100644 --- a/apct-tests/perftests/windowmanager/AndroidTest.xml +++ b/apct-tests/perftests/windowmanager/AndroidTest.xml @@ -21,9 +21,17 @@ <option name="test-file-name" value="WmPerfTests.apk" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="force-skip-system-props" value="true" /> + <option name="run-command" value="input keyevent KEYCODE_WAKEUP" /> + <option name="run-command" value="cmd window dismiss-keyguard" /> + <option name="run-command" value="cmd package compile -m speed com.android.perftests.wm" /> + </target_preparer> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > <option name="package" value="com.android.perftests.wm" /> <option name="hidden-api-checks" value="false"/> + <option name="device-listeners" value="android.wm.WmPerfRunListener" /> </test> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/apct-tests/perftests/windowmanager/README.md b/apct-tests/perftests/windowmanager/README.md new file mode 100644 index 000000000000..05fa6279a949 --- /dev/null +++ b/apct-tests/perftests/windowmanager/README.md @@ -0,0 +1,27 @@ +## Window manager performance tests + +### Precondition +To reduce the variance of the test, if `perf-setup.sh` (platform_testing/scripts/perf-setup) +is available, it is better to use the following instructions to lock CPU and GPU frequencies. +``` +m perf-setup.sh +PERF_SETUP_PATH=/data/local/tmp/perf-setup.sh +adb push $OUT/$PERF_SETUP_PATH $PERF_SETUP_PATH +adb shell chmod +x $PERF_SETUP_PATH +adb shell $PERF_SETUP_PATH +``` + +### Example to run +Use `atest` +``` +atest WmPerfTests:RelayoutPerfTest -- \ + --module-arg WmPerfTests:instrumentation-arg:kill-bg:=true +``` +Use `am instrument` +``` +adb shell am instrument -w -r -e class android.wm.RelayoutPerfTest \ + -e listener android.wm.WmPerfRunListener \ + -e kill-bg true \ + com.android.perftests.wm/androidx.test.runner.AndroidJUnitRunner +``` +* `kill-bg` is optional. diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java index f04e55567520..cff5663e9d9e 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RelayoutPerfTest.java @@ -26,6 +26,7 @@ import android.os.RemoteException; import android.perftests.utils.BenchmarkState; import android.perftests.utils.PerfStatusReporter; import android.perftests.utils.PerfTestActivity; +import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.view.DisplayCutout; import android.view.IWindow; @@ -52,6 +53,7 @@ import java.util.function.IntSupplier; @RunWith(Parameterized.class) @LargeTest +@Presubmit public class RelayoutPerfTest extends WindowManagerPerfTestBase { private int mIteration; diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java index 655d2f7f8aa7..dc6245bf2c09 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowManagerPerfTestBase.java @@ -23,18 +23,15 @@ import android.app.KeyguardManager; import android.app.UiAutomation; import android.content.Context; import android.content.Intent; -import android.os.BatteryManager; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.perftests.utils.PerfTestActivity; -import android.provider.Settings; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.lifecycle.ActivityLifecycleCallback; import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry; import androidx.test.runner.lifecycle.Stage; -import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -43,7 +40,9 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; public class WindowManagerPerfTestBase { static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation(); @@ -56,21 +55,11 @@ public class WindowManagerPerfTestBase { * is in /data because while enabling method profling of system server, it cannot write the * trace to external storage. */ - static final File BASE_OUT_PATH = new File("/data/local/CorePerfTests"); - - private static int sOriginalStayOnWhilePluggedIn; + static final File BASE_OUT_PATH = new File("/data/local/WmPerfTests"); @BeforeClass public static void setUpOnce() { final Context context = getInstrumentation().getContext(); - final int stayOnWhilePluggedIn = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); - sOriginalStayOnWhilePluggedIn = -1; - if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) { - sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn; - // Keep the device awake during testing. - setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY); - } if (!BASE_OUT_PATH.exists()) { executeShellCommand("mkdir -p " + BASE_OUT_PATH); @@ -84,18 +73,6 @@ public class WindowManagerPerfTestBase { .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } - @AfterClass - public static void tearDownOnce() { - if (sOriginalStayOnWhilePluggedIn != -1) { - setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn); - } - } - - private static void setStayOnWhilePluggedIn(int value) { - executeShellCommand(String.format("settings put global %s %d", - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value)); - } - /** * Executes shell command with reading the output. It may also used to block until the current * command is completed. @@ -124,6 +101,42 @@ public class WindowManagerPerfTestBase { executeShellCommand("am profile stop system"); } + static void runWithShellPermissionIdentity(Runnable runnable) { + sUiAutomation.adoptShellPermissionIdentity(); + try { + runnable.run(); + } finally { + sUiAutomation.dropShellPermissionIdentity(); + } + } + + static class SettingsSession<T> implements AutoCloseable { + private final Consumer<T> mSetter; + private final T mOriginalValue; + private boolean mChanged; + + SettingsSession(T originalValue, Consumer<T> setter) { + mOriginalValue = originalValue; + mSetter = setter; + } + + void set(T value) { + if (Objects.equals(value, mOriginalValue)) { + mChanged = false; + return; + } + mSetter.accept(value); + mChanged = true; + } + + @Override + public void close() { + if (mChanged) { + mSetter.accept(mOriginalValue); + } + } + } + /** * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage. */ diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java new file mode 100644 index 000000000000..6eb85aacb4e8 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/android/wm/WmPerfRunListener.java @@ -0,0 +1,130 @@ +/* + * 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.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; +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.wm.WindowManagerPerfTestBase.executeShellCommand; +import static android.wm.WindowManagerPerfTestBase.runWithShellPermissionIdentity; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.ActivityTaskManager; +import android.content.Context; +import android.os.BatteryManager; +import android.os.Bundle; +import android.os.SystemClock; +import android.provider.Settings; +import android.view.WindowManagerPolicyConstants; +import android.wm.WindowManagerPerfTestBase.SettingsSession; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.internal.policy.PhoneWindow; + +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.notification.RunListener; + +import java.util.List; + +/** Prepare the preconditions before running performance test. */ +public class WmPerfRunListener extends RunListener { + + private static final String OPTION_KILL_BACKGROUND = "kill-bg"; + private static final long KILL_BACKGROUND_WAIT_MS = 3000; + + private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext(); + private long mWaitPreconditionDoneMs = 500; + + private final SettingsSession<Integer> mStayOnWhilePluggedInSetting = new SettingsSession<>( + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0), + value -> executeShellCommand(String.format("settings put global %s %d", + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value))); + + private final SettingsSession<Integer> mNavigationModeSetting = new SettingsSession<>( + mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode), + value -> { + final String navOverlay; + switch (value) { + case WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON: + navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY; + break; + case WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON: + navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; + break; + case WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL: + default: + navOverlay = WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; + break; + } + executeShellCommand("cmd overlay enable-exclusive " + navOverlay); + }); + + /** It only executes once before all tests. */ + @Override + public void testRunStarted(Description description) { + final Bundle arguments = InstrumentationRegistry.getArguments(); + + // Use gesture navigation for consistency. + mNavigationModeSetting.set(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL); + // Keep the device awake during testing. + mStayOnWhilePluggedInSetting.set(BatteryManager.BATTERY_PLUGGED_ANY); + + runWithShellPermissionIdentity(() -> { + final ActivityTaskManager atm = mContext.getSystemService(ActivityTaskManager.class); + atm.removeAllVisibleRecentTasks(); + atm.removeStacksWithActivityTypes(new int[] { ACTIVITY_TYPE_STANDARD, + ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, ACTIVITY_TYPE_UNDEFINED }); + }); + PhoneWindow.sendCloseSystemWindows(mContext, "WmPerfTests"); + + if (Boolean.parseBoolean(arguments.getString(OPTION_KILL_BACKGROUND))) { + runWithShellPermissionIdentity(this::killBackgroundProcesses); + mWaitPreconditionDoneMs = KILL_BACKGROUND_WAIT_MS; + } + // Wait a while for the precondition setup to complete. + SystemClock.sleep(mWaitPreconditionDoneMs); + } + + private void killBackgroundProcesses() { + final ActivityManager am = mContext.getSystemService(ActivityManager.class); + final List<RunningAppProcessInfo> processes = am.getRunningAppProcesses(); + if (processes == null) { + return; + } + for (RunningAppProcessInfo processInfo : processes) { + if (processInfo.importanceReasonCode == RunningAppProcessInfo.REASON_UNKNOWN + && processInfo.importance > RunningAppProcessInfo.IMPORTANCE_SERVICE) { + for (String pkg : processInfo.pkgList) { + am.forceStopPackage(pkg); + } + } + } + } + + /** It only executes once after all tests. */ + @Override + public void testRunFinished(Result result) { + mNavigationModeSetting.close(); + mStayOnWhilePluggedInSetting.close(); + } +} diff --git a/apct-tests/perftests/windowmanager/src/com/android/server/wm/test/filters/FrameworksTestsFilter.java b/apct-tests/perftests/windowmanager/src/com/android/server/wm/test/filters/FrameworksTestsFilter.java new file mode 100644 index 000000000000..d018287986f2 --- /dev/null +++ b/apct-tests/perftests/windowmanager/src/com/android/server/wm/test/filters/FrameworksTestsFilter.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.server.wm.test.filters; + +import android.wm.RelayoutPerfTest; + +import org.junit.runner.Description; +import org.junit.runner.manipulation.Filter; + +/** + * A static filter to have the same signature as the one in frameworks/base/tests/utils/testutils/. + * This doesn't share the existing library because it doesn't support parameterized test. + */ +public class FrameworksTestsFilter extends Filter { + + private boolean mShouldRun; + + @Override + public boolean shouldRun(Description description) { + final Class<?> testClass = description.getTestClass(); + // Parameterized test methods don't have the original information. So keep the last status + // that matches the target class. + mShouldRun = (mShouldRun && testClass == null) || testClass == RelayoutPerfTest.class; + return mShouldRun; + } + + @Override + public String describe() { + return "Default filter"; + } +} diff --git a/api/current.txt b/api/current.txt index 7119902b07d3..1d2286b333bb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -24079,6 +24079,7 @@ package android.media { field public static final int TYPE_IP = 20; // 0x14 field public static final int TYPE_LINE_ANALOG = 5; // 0x5 field public static final int TYPE_LINE_DIGITAL = 6; // 0x6 + field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 field public static final int TYPE_TELEPHONY = 18; // 0x12 field public static final int TYPE_TV_TUNER = 17; // 0x11 field public static final int TYPE_UNKNOWN = 0; // 0x0 diff --git a/api/removed.txt b/api/removed.txt index 5a24f625d146..58dbeb8a7d3f 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -1,10 +1,6 @@ // Signature format: 2.0 package android.app { - public class ActivityManager { - method @Deprecated public static int getMaxNumPictureInPictureActions(); - } - public class Notification implements android.os.Parcelable { method @Deprecated public String getChannel(); method public static Class<? extends android.app.Notification.Style> getNotificationStyleClass(String); diff --git a/api/system-current.txt b/api/system-current.txt index fa4543001f90..11db781dd42f 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4172,10 +4172,6 @@ package android.media { field public static final int ROLE_OUTPUT = 2; // 0x2 } - public final class AudioDeviceInfo { - field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 - } - public final class AudioFocusInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.media.AudioAttributes getAttributes(); diff --git a/api/test-current.txt b/api/test-current.txt index dc6626586efa..f1a3566f7b45 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -20,6 +20,7 @@ package android { field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS"; field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE"; field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS"; + field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS"; field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; @@ -83,6 +84,7 @@ package android.app { method public static boolean isHighEndGfx(); method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void killProcessesWhenImperceptible(@NonNull int[], @NonNull String); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener); + method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int); method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle); @@ -3292,6 +3294,7 @@ package android.provider { field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky"; field public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; + field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog"; field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"; field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package"; } @@ -3300,6 +3303,7 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String); field public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED = "accessibility_display_magnification_enabled"; field public static final String ACCESSIBILITY_SHORTCUT_TARGET_SERVICE = "accessibility_shortcut_target_service"; + field public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; field public static final String AUTOFILL_FEATURE_FIELD_CLASSIFICATION = "autofill_field_classification"; field public static final String AUTOFILL_SERVICE = "autofill_service"; field public static final String AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT = "autofill_user_data_max_category_count"; @@ -3320,6 +3324,7 @@ package android.provider { field public static final String NFC_PAYMENT_DEFAULT_COMPONENT = "nfc_payment_default_component"; field public static final String NOTIFICATION_BADGING = "notification_badging"; field public static final String POWER_MENU_LOCKED_SHOW_CONTENT = "power_menu_locked_show_content"; + field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option"; field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String USER_SETUP_COMPLETE = "user_setup_complete"; diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7c4c19dde4d0..a88c6a890844 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1019,12 +1019,6 @@ public class ActivityManager { return ActivityTaskManager.getMaxRecentTasksStatic(); } - /** @removed */ - @Deprecated - public static int getMaxNumPictureInPictureActions() { - return 3; - } - /** * Information you can set and retrieve about the current activity within the recent task list. */ @@ -3739,7 +3733,8 @@ public class ActivityManager { * manner, excessive calls to this API could result a {@link java.lang.RuntimeException}. * </p> * - * @param state The state data + * @param state The state data. To be advised, <b>DO NOT</b> include sensitive information/data + * (PII, SPII, or other sensitive user data) here. Maximum length is 128 bytes. */ public void setProcessStateSummary(@Nullable byte[] state) { try { @@ -4941,4 +4936,19 @@ public class ActivityManager { throw e.rethrowFromSystemServer(); } } + + /** + * Resets the state of the {@link com.android.server.am.AppErrors} instance. + * This is intended for use with CTS only. + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.RESET_APP_ERRORS) + public void resetAppErrors() { + try { + getService().resetAppErrors(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 7ba50cadd959..9e15c1fca077 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -103,17 +103,16 @@ public abstract class ActivityManagerInternal { IBinder whitelistToken, long duration); /** - * Allows for a {@link PendingIntent} to be whitelisted to start activities from background. + * Allows a {@link PendingIntent} to start activities from background. */ public abstract void setPendingIntentAllowBgActivityStarts( - IIntentSender target, IBinder whitelistToken, int flags); + IIntentSender target, IBinder allowlistToken, int flags); /** - * Voids {@link PendingIntent}'s privilege to be whitelisted to start activities from - * background. + * Voids {@link PendingIntent}'s privilege to start activities from background. */ public abstract void clearPendingIntentAllowBgActivityStarts(IIntentSender target, - IBinder whitelistToken); + IBinder allowlistToken); /** * Allow DeviceIdleController to tell us about what apps are whitelisted. diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index bac432e42318..15237beee805 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -48,17 +48,18 @@ public class ApplicationLoaders { ClassLoader parent, String classLoaderName) { return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, librarySearchPath, libraryPermittedPath, parent, classLoaderName, - null); + null, null); } ClassLoader getClassLoaderWithSharedLibraries( String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String classLoaderName, - List<ClassLoader> sharedLibraries) { + List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) { // For normal usage the cache key used is the same as the zip path. return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath, - libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries); + libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries, + nativeSharedLibraries); } /** @@ -77,14 +78,22 @@ public class ApplicationLoaders { return loader; } + // TODO(b/142191088): allow (Java) shared libraries to have <uses-native-library> + // Until that is supported, assume that all native shared libraries are used. + // "ALL" is a magic string that libnativeloader uses to unconditionally add all available + // native shared libraries to the classloader. + List<String> nativeSharedLibraries = new ArrayList<>(); + nativeSharedLibraries.add("ALL"); return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled, - librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries); + librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries, + nativeSharedLibraries); } private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey, - String classLoaderName, List<ClassLoader> sharedLibraries) { + String classLoaderName, List<ClassLoader> sharedLibraries, + List<String> nativeSharedLibraries) { /* * This is the parent we use if they pass "null" in. In theory * this should be the "system" class loader; in practice we @@ -113,7 +122,8 @@ public class ApplicationLoaders { ClassLoader classloader = ClassLoaderFactory.createClassLoader( zip, librarySearchPath, libraryPermittedPath, parent, - targetSdkVersion, isBundled, classLoaderName, sharedLibraries); + targetSdkVersion, isBundled, classLoaderName, sharedLibraries, + nativeSharedLibraries); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -185,7 +195,8 @@ public class ApplicationLoaders { // assume cached libraries work with current sdk since they are built-in ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/, null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/, - null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/); + null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/, + null /* nativeSharedLibraries */); if (classLoader == null) { // bad configuration or break in classloading code @@ -255,7 +266,8 @@ public class ApplicationLoaders { // The cache key is passed separately to enable the stub WebView to be cached under the // stub's APK path, when the actual package path is the donor APK. return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null, - cacheKey, null /* classLoaderName */, null /* sharedLibraries */); + cacheKey, null /* classLoaderName */, null /* sharedLibraries */, + null /* nativeSharedLibraries */); } /** diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index befd58824e63..3b6a7b8f7592 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -686,4 +686,11 @@ interface IActivityManager { * Kills uid with the reason of permission change. */ void killUidForPermissionChange(int appId, int userId, String reason); + + /** + * Resets the state of the {@link com.android.server.am.AppErrors} instance. + * This is intended for testing within the CTS only and is protected by + * android.permission.RESET_APP_ERRORS. + */ + void resetAppErrors(); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index f9b48e710148..1dc54ddbac4b 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -412,6 +412,12 @@ public final class LoadedApk { return; } for (SharedLibraryInfo lib : sharedLibraries) { + if (lib.isNative()) { + // Native shared lib doesn't contribute to the native lib search path. Its name is + // sent to libnativeloader and then the native shared lib is exported from the + // default linker namespace. + continue; + } List<String> paths = lib.getAllCodePaths(); outSeenPaths.addAll(paths); for (String path : paths) { @@ -696,6 +702,12 @@ public final class LoadedApk { } List<ClassLoader> loaders = new ArrayList<>(); for (SharedLibraryInfo info : sharedLibraries) { + if (info.isNative()) { + // Native shared lib doesn't contribute to the native lib search path. Its name is + // sent to libnativeloader and then the native shared lib is exported from the + // default linker namespace. + continue; + } loaders.add(createSharedLibraryLoader( info, isBundledApp, librarySearchPath, libraryPermittedPath)); } @@ -898,10 +910,19 @@ public final class LoadedApk { mApplicationInfo.sharedLibraryInfos, isBundledApp, librarySearchPath, libraryPermittedPath); + List<String> nativeSharedLibraries = new ArrayList<>(); + if (mApplicationInfo.sharedLibraryInfos != null) { + for (SharedLibraryInfo info : mApplicationInfo.sharedLibraryInfos) { + if (info.isNative()) { + nativeSharedLibraries.add(info.getName()); + } + } + } + mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries( zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath, libraryPermittedPath, mBaseClassLoader, - mApplicationInfo.classLoaderName, sharedLibraries); + mApplicationInfo.classLoaderName, sharedLibraries, nativeSharedLibraries); mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader); setThreadPolicy(oldPolicy); diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java index 5f6befdcbaef..e990fd783498 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -525,6 +525,9 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) { protLevel += "|appPredictor"; } + if ((level & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0) { + protLevel += "|companion"; + } if ((level & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0) { protLevel += "|retailDemo"; } diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index da2a3d885fc6..862563706da7 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -79,6 +79,7 @@ public final class SharedLibraryInfo implements Parcelable { private final long mVersion; private final @Type int mType; + private final boolean mIsNative; private final VersionedPackage mDeclaringPackage; private final List<VersionedPackage> mDependentPackages; private List<SharedLibraryInfo> mDependencies; @@ -93,13 +94,14 @@ public final class SharedLibraryInfo implements Parcelable { * @param type The lib type. * @param declaringPackage The package that declares the library. * @param dependentPackages The packages that depend on the library. + * @param isNative indicate if this shared lib is a native lib or not (i.e. java) * * @hide */ public SharedLibraryInfo(String path, String packageName, List<String> codePaths, String name, long version, int type, VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, - List<SharedLibraryInfo> dependencies) { + List<SharedLibraryInfo> dependencies, boolean isNative) { mPath = path; mPackageName = packageName; mCodePaths = codePaths; @@ -109,6 +111,16 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = declaringPackage; mDependentPackages = dependentPackages; mDependencies = dependencies; + mIsNative = isNative; + } + + /** @hide */ + public SharedLibraryInfo(String path, String packageName, List<String> codePaths, + String name, long version, int type, + VersionedPackage declaringPackage, List<VersionedPackage> dependentPackages, + List<SharedLibraryInfo> dependencies) { + this(path, packageName, codePaths, name, version, type, declaringPackage, dependentPackages, + dependencies, false /* isNative */); } private SharedLibraryInfo(Parcel parcel) { @@ -125,6 +137,7 @@ public final class SharedLibraryInfo implements Parcelable { mDeclaringPackage = parcel.readParcelable(null); mDependentPackages = parcel.readArrayList(null); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); + mIsNative = parcel.readBoolean(); } /** @@ -137,6 +150,15 @@ public final class SharedLibraryInfo implements Parcelable { } /** + * Tells whether this library is a native shared library or not. + * + * @hide + */ + public boolean isNative() { + return mIsNative; + } + + /** * Gets the library name an app defines in its manifest * to depend on the library. * @@ -320,6 +342,7 @@ public final class SharedLibraryInfo implements Parcelable { parcel.writeParcelable(mDeclaringPackage, flags); parcel.writeList(mDependentPackages); parcel.writeTypedList(mDependencies); + parcel.writeBoolean(mIsNative); } private static String typeToString(int type) { diff --git a/core/java/android/content/pm/parsing/ParsingPackage.java b/core/java/android/content/pm/parsing/ParsingPackage.java index 2ee0ad67b108..872098c8689e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackage.java +++ b/core/java/android/content/pm/parsing/ParsingPackage.java @@ -92,6 +92,10 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage addUsesOptionalLibrary(String libraryName); + ParsingPackage addUsesNativeLibrary(String libraryName); + + ParsingPackage addUsesOptionalNativeLibrary(String libraryName); + ParsingPackage addUsesStaticLibrary(String libraryName); ParsingPackage addUsesStaticLibraryCertDigests(String[] certSha256Digests); @@ -219,6 +223,8 @@ public interface ParsingPackage extends ParsingPackageRead { ParsingPackage removeUsesOptionalLibrary(String libraryName); + ParsingPackage removeUsesOptionalNativeLibrary(String libraryName); + ParsingPackage setAnyDensity(int anyDensity); ParsingPackage setAppComponentFactory(String appComponentFactory); diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java index f932bc250e28..0c0dc313087e 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java +++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java @@ -186,6 +186,13 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { @NonNull @DataClass.ParcelWith(ForInternedStringList.class) + protected List<String> usesNativeLibraries = emptyList(); + @NonNull + @DataClass.ParcelWith(ForInternedStringList.class) + protected List<String> usesOptionalNativeLibraries = emptyList(); + + @NonNull + @DataClass.ParcelWith(ForInternedStringList.class) private List<String> usesStaticLibraries = emptyList(); @Nullable private long[] usesStaticLibrariesVersions; @@ -669,6 +676,27 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { } @Override + public final ParsingPackageImpl addUsesOptionalNativeLibrary(String libraryName) { + this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries, + TextUtils.safeIntern(libraryName)); + return this; + } + + @Override + public final ParsingPackageImpl addUsesNativeLibrary(String libraryName) { + this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries, + TextUtils.safeIntern(libraryName)); + return this; + } + + + @Override public ParsingPackageImpl removeUsesOptionalNativeLibrary(String libraryName) { + this.usesOptionalNativeLibraries = CollectionUtils.remove(this.usesOptionalNativeLibraries, + libraryName); + return this; + } + + @Override public ParsingPackageImpl addUsesStaticLibrary(String libraryName) { this.usesStaticLibraries = CollectionUtils.add(this.usesStaticLibraries, TextUtils.safeIntern(libraryName)); @@ -982,6 +1010,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { sForInternedStringList.parcel(this.libraryNames, dest, flags); sForInternedStringList.parcel(this.usesLibraries, dest, flags); sForInternedStringList.parcel(this.usesOptionalLibraries, dest, flags); + sForInternedStringList.parcel(this.usesNativeLibraries, dest, flags); + sForInternedStringList.parcel(this.usesOptionalNativeLibraries, dest, flags); sForInternedStringList.parcel(this.usesStaticLibraries, dest, flags); dest.writeLongArray(this.usesStaticLibrariesVersions); @@ -1144,6 +1174,8 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { this.libraryNames = sForInternedStringList.unparcel(in); this.usesLibraries = sForInternedStringList.unparcel(in); this.usesOptionalLibraries = sForInternedStringList.unparcel(in); + this.usesNativeLibraries = sForInternedStringList.unparcel(in); + this.usesOptionalNativeLibraries = sForInternedStringList.unparcel(in); this.usesStaticLibraries = sForInternedStringList.unparcel(in); this.usesStaticLibrariesVersions = in.createLongArray(); @@ -1417,6 +1449,18 @@ public class ParsingPackageImpl implements ParsingPackage, Parcelable { @NonNull @Override + public List<String> getUsesNativeLibraries() { + return usesNativeLibraries; + } + + @NonNull + @Override + public List<String> getUsesOptionalNativeLibraries() { + return usesOptionalNativeLibraries; + } + + @NonNull + @Override public List<String> getUsesStaticLibraries() { return usesStaticLibraries; } diff --git a/core/java/android/content/pm/parsing/ParsingPackageRead.java b/core/java/android/content/pm/parsing/ParsingPackageRead.java index 5b53c18b820c..7e0fe7dc41bf 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageRead.java +++ b/core/java/android/content/pm/parsing/ParsingPackageRead.java @@ -230,6 +230,19 @@ public interface ParsingPackageRead extends Parcelable { @NonNull List<String> getUsesOptionalLibraries(); + /** @see R.styleabele#AndroidManifestUsesNativeLibrary */ + @NonNull + List<String> getUsesNativeLibraries(); + + /** + * Like {@link #getUsesNativeLibraries()}, but marked optional by setting + * {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is + * expected to handle absence manually. + * @see R.styleable#AndroidManifestUsesNativeLibrary + */ + @NonNull + List<String> getUsesOptionalNativeLibraries(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * @see R.styleable#AndroidManifestUsesStaticLibrary diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java index 3688f1bda979..e1f08f3e55a1 100644 --- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java @@ -701,6 +701,8 @@ public class ParsingPackageUtils { return parseUsesStaticLibrary(input, pkg, res, parser); case "uses-library": return parseUsesLibrary(input, pkg, res, parser); + case "uses-native-library": + return parseUsesNativeLibrary(input, pkg, res, parser); case "uses-package": // Dependencies for app installers; we don't currently try to // enforce this. @@ -2017,6 +2019,8 @@ public class ParsingPackageUtils { return parseUsesStaticLibrary(input, pkg, res, parser); case "uses-library": return parseUsesLibrary(input, pkg, res, parser); + case "uses-native-library": + return parseUsesNativeLibrary(input, pkg, res, parser); case "processes": return parseProcesses(input, pkg, res, parser, mSeparateProcesses, flags); case "uses-package": @@ -2178,6 +2182,37 @@ public class ParsingPackageUtils { } @NonNull + private static ParseResult<ParsingPackage> parseUsesNativeLibrary(ParseInput input, + ParsingPackage pkg, Resources res, XmlResourceParser parser) { + TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestUsesNativeLibrary); + try { + // Note: don't allow this value to be a reference to a resource + // that may change. + String lname = sa.getNonResourceString( + R.styleable.AndroidManifestUsesNativeLibrary_name); + boolean req = sa.getBoolean(R.styleable.AndroidManifestUsesNativeLibrary_required, + true); + + if (lname != null) { + if (req) { + // Upgrade to treat as stronger constraint + pkg.addUsesNativeLibrary(lname) + .removeUsesOptionalNativeLibrary(lname); + } else { + // Ignore if someone already defined as required + if (!ArrayUtils.contains(pkg.getUsesNativeLibraries(), lname)) { + pkg.addUsesOptionalNativeLibrary(lname); + } + } + } + + return input.success(pkg); + } finally { + sa.recycle(); + } + } + + @NonNull private static ParseResult<ParsingPackage> parseProcesses(ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser, String[] separateProcesses, int flags) throws IOException, XmlPullParserException { diff --git a/core/java/android/metrics/LogMaker.java b/core/java/android/metrics/LogMaker.java index 5496e17206d9..d8a2082f4eae 100644 --- a/core/java/android/metrics/LogMaker.java +++ b/core/java/android/metrics/LogMaker.java @@ -39,7 +39,7 @@ public class LogMaker { /** * Min required eventlog line length. * See: android/util/cts/EventLogTest.java - * Size checks enforced here are intended only as sanity checks; + * Size limits enforced here are intended only as a precaution; * your logs may be truncated earlier. Please log responsibly. * * @hide diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java index 9b360edb7238..bedbba04255e 100644 --- a/core/java/android/os/Parcelable.java +++ b/core/java/android/os/Parcelable.java @@ -99,6 +99,35 @@ public interface Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface ContentsFlags {} + /** @hide */ + @IntDef(flag = true, prefix = { "PARCELABLE_STABILITY_" }, value = { + PARCELABLE_STABILITY_LOCAL, + PARCELABLE_STABILITY_VINTF, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Stability {} + + /** + * Something that is not meant to cross compilation boundaries. + * + * Note: unlike binder/Stability.h which uses bitsets to detect stability, + * since we don't currently have a notion of different local locations, + * higher stability levels are formed at higher levels. + * + * For instance, contained entirely within system partitions. + * @see #getStability() + * @see ParcelableHolder + * @hide + */ + public static final int PARCELABLE_STABILITY_LOCAL = 0x0000; + /** + * Something that is meant to be used between system and vendor. + * @see #getStability() + * @see ParcelableHolder + * @hide + */ + public static final int PARCELABLE_STABILITY_VINTF = 0x0001; + /** * Descriptor bit used with {@link #describeContents()}: indicates that * the Parcelable object's flattened representation includes a file descriptor. @@ -129,8 +158,8 @@ public interface Parcelable { * @return true if this parcelable is stable. * @hide */ - default boolean isStable() { - return false; + default @Stability int getStability() { + return PARCELABLE_STABILITY_LOCAL; } /** diff --git a/core/java/android/os/ParcelableHolder.java b/core/java/android/os/ParcelableHolder.java index c37a2ff1112c..181f94b39841 100644 --- a/core/java/android/os/ParcelableHolder.java +++ b/core/java/android/os/ParcelableHolder.java @@ -37,10 +37,10 @@ public final class ParcelableHolder implements Parcelable { * if {@link ParcelableHolder} contains value, otherwise, both are null. */ private Parcel mParcel; - private boolean mIsStable = false; + private @Parcelable.Stability int mStability = Parcelable.PARCELABLE_STABILITY_LOCAL; - public ParcelableHolder(boolean isStable) { - mIsStable = isStable; + public ParcelableHolder(@Parcelable.Stability int stability) { + mStability = stability; } private ParcelableHolder() { @@ -50,11 +50,11 @@ public final class ParcelableHolder implements Parcelable { /** * {@link ParcelableHolder}'s stability is determined by the parcelable * which contains this ParcelableHolder. - * For more detail refer to {@link Parcelable#isStable}. + * For more detail refer to {@link Parcelable#getStability}. */ @Override - public boolean isStable() { - return mIsStable; + public @Parcelable.Stability int getStability() { + return mStability; } @NonNull @@ -81,7 +81,8 @@ public final class ParcelableHolder implements Parcelable { * @return {@code false} if the parcelable's stability is more unstable ParcelableHolder. */ public synchronized boolean setParcelable(@Nullable Parcelable p) { - if (p != null && this.isStable() && !p.isStable()) { + // a ParcelableHolder can only hold things at its stability or higher + if (p != null && this.getStability() > p.getStability()) { return false; } mParcelable = p; @@ -123,7 +124,7 @@ public final class ParcelableHolder implements Parcelable { * Read ParcelableHolder from a parcel. */ public synchronized void readFromParcel(@NonNull Parcel parcel) { - this.mIsStable = parcel.readBoolean(); + this.mStability = parcel.readInt(); mParcelable = null; @@ -145,7 +146,7 @@ public final class ParcelableHolder implements Parcelable { @Override public synchronized void writeToParcel(@NonNull Parcel parcel, int flags) { - parcel.writeBoolean(this.mIsStable); + parcel.writeInt(this.mStability); if (mParcel != null) { parcel.writeInt(mParcel.dataSize()); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 500e476a7b80..346522a504c8 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7614,6 +7614,8 @@ public final class Settings { * @hide */ @UnsupportedAppUsage + @TestApi + @SuppressLint("NoSettingsProvider") public static final String ANR_SHOW_BACKGROUND = "anr_show_background"; /** @@ -7621,6 +7623,8 @@ public final class Settings { * Otherwise, the process will be silently killed. * @hide */ + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option"; @@ -14180,6 +14184,8 @@ public final class Settings { * Otherwise, the process will be silently killed. * @hide */ + @TestApi + @SuppressLint("NoSettingsProvider") public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog"; /** diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index e3cb382256ae..ab19fa9a1256 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -551,7 +551,7 @@ public class Html { out.append(((ImageSpan) style[j]).getSource()); out.append("\">"); - // Don't output the dummy character underlying the image. + // Don't output the placeholder character underlying the image. i = next; } if (style[j] instanceof AbsoluteSizeSpan) { diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 4fe6752be4d5..38e3b39f8cfc 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -26,8 +26,6 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; -import libcore.icu.LocaleData; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; @@ -287,8 +285,10 @@ public class DateFormat { */ @UnsupportedAppUsage public static String getTimeFormatString(Context context, int userHandle) { - final LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); - return is24HourFormat(context, userHandle) ? d.timeFormat_Hm : d.timeFormat_hm; + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance( + context.getResources().getConfiguration().locale); + return is24HourFormat(context, userHandle) ? dtpg.getBestPattern("Hm") + : dtpg.getBestPattern("hm"); } /** @@ -475,7 +475,6 @@ public class DateFormat { SpannableStringBuilder s = new SpannableStringBuilder(inFormat); int count; - LocaleData localeData = LocaleData.get(Locale.getDefault()); DateFormatSymbols dfs = getIcuDateFormatSymbols(Locale.getDefault()); String[] amPm = dfs.getAmPmStrings(); @@ -506,7 +505,7 @@ public class DateFormat { break; case 'c': case 'E': - replacement = getDayOfWeekString(localeData, + replacement = getDayOfWeekString(dfs, inDate.get(Calendar.DAY_OF_WEEK), count, c); break; case 'K': // hour in am/pm (0-11) @@ -534,8 +533,7 @@ public class DateFormat { break; case 'L': case 'M': - replacement = getMonthString(localeData, - inDate.get(Calendar.MONTH), count, c); + replacement = getMonthString(dfs, inDate.get(Calendar.MONTH), count, c); break; case 'm': replacement = zeroPad(inDate.get(Calendar.MINUTE), count); @@ -568,25 +566,29 @@ public class DateFormat { } } - private static String getDayOfWeekString(LocaleData ld, int day, int count, int kind) { + private static String getDayOfWeekString(DateFormatSymbols dfs, int day, int count, int kind) { boolean standalone = (kind == 'c'); + int context = standalone ? DateFormatSymbols.STANDALONE : DateFormatSymbols.FORMAT; + final int width; if (count == 5) { - return standalone ? ld.tinyStandAloneWeekdayNames[day] : ld.tinyWeekdayNames[day]; + width = DateFormatSymbols.NARROW; } else if (count == 4) { - return standalone ? ld.longStandAloneWeekdayNames[day] : ld.longWeekdayNames[day]; + width = DateFormatSymbols.WIDE; } else { - return standalone ? ld.shortStandAloneWeekdayNames[day] : ld.shortWeekdayNames[day]; + width = DateFormatSymbols.ABBREVIATED; } + return dfs.getWeekdays(context, width)[day]; } - private static String getMonthString(LocaleData ld, int month, int count, int kind) { + private static String getMonthString(DateFormatSymbols dfs, int month, int count, int kind) { boolean standalone = (kind == 'L'); + int monthContext = standalone ? DateFormatSymbols.STANDALONE : DateFormatSymbols.FORMAT; if (count == 5) { - return standalone ? ld.tinyStandAloneMonthNames[month] : ld.tinyMonthNames[month]; + return dfs.getMonths(monthContext, DateFormatSymbols.NARROW)[month]; } else if (count == 4) { - return standalone ? ld.longStandAloneMonthNames[month] : ld.longMonthNames[month]; + return dfs.getMonths(monthContext, DateFormatSymbols.WIDE)[month]; } else if (count == 3) { - return standalone ? ld.shortStandAloneMonthNames[month] : ld.shortMonthNames[month]; + return dfs.getMonths(monthContext, DateFormatSymbols.ABBREVIATED)[month]; } else { // Calendar.JANUARY == 0, so add 1 to month. return zeroPad(month+1, count); diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 741312f2337e..ff08269a93a4 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -20,6 +20,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.icu.text.DateFormatSymbols; import android.icu.text.MeasureFormat; import android.icu.text.MeasureFormat.FormatWidth; import android.icu.util.Measure; @@ -27,8 +28,6 @@ import android.icu.util.MeasureUnit; import com.android.internal.R; -import libcore.icu.LocaleData; - import java.io.IOException; import java.time.Instant; import java.time.LocalDateTime; @@ -203,17 +202,23 @@ public class DateUtils */ @Deprecated public static String getDayOfWeekString(int dayOfWeek, int abbrev) { - LocaleData d = LocaleData.get(Locale.getDefault()); - String[] names; + DateFormatSymbols dfs = DateFormatSymbols.getInstance(); + final int width; switch (abbrev) { - case LENGTH_LONG: names = d.longWeekdayNames; break; - case LENGTH_MEDIUM: names = d.shortWeekdayNames; break; - case LENGTH_SHORT: names = d.shortWeekdayNames; break; // TODO - case LENGTH_SHORTER: names = d.shortWeekdayNames; break; // TODO - case LENGTH_SHORTEST: names = d.tinyWeekdayNames; break; - default: names = d.shortWeekdayNames; break; + case LENGTH_LONG: + width = DateFormatSymbols.WIDE; + break; + case LENGTH_SHORTEST: + width = DateFormatSymbols.NARROW; + break; + case LENGTH_MEDIUM: + case LENGTH_SHORT: // TODO + case LENGTH_SHORTER: // TODO + default: + width = DateFormatSymbols.ABBREVIATED; + break; } - return names[dayOfWeek]; + return dfs.getWeekdays(DateFormatSymbols.FORMAT, width)[dayOfWeek]; } /** @@ -242,17 +247,23 @@ public class DateUtils */ @Deprecated public static String getMonthString(int month, int abbrev) { - LocaleData d = LocaleData.get(Locale.getDefault()); - String[] names; + DateFormatSymbols dfs = DateFormat.getIcuDateFormatSymbols(Locale.getDefault()); + final int width; switch (abbrev) { - case LENGTH_LONG: names = d.longMonthNames; break; - case LENGTH_MEDIUM: names = d.shortMonthNames; break; - case LENGTH_SHORT: names = d.shortMonthNames; break; - case LENGTH_SHORTER: names = d.shortMonthNames; break; - case LENGTH_SHORTEST: names = d.tinyMonthNames; break; - default: names = d.shortMonthNames; break; + case LENGTH_LONG: + width = DateFormatSymbols.WIDE; + break; + case LENGTH_SHORTEST: + width = DateFormatSymbols.NARROW; + break; + case LENGTH_MEDIUM: + case LENGTH_SHORT: + case LENGTH_SHORTER: + default: + width = DateFormatSymbols.ABBREVIATED; + break; } - return names[month]; + return dfs.getMonths(DateFormatSymbols.FORMAT, width)[month]; } /** diff --git a/core/java/android/text/format/OWNERS b/core/java/android/text/format/OWNERS new file mode 100644 index 000000000000..32adc12bb901 --- /dev/null +++ b/core/java/android/text/format/OWNERS @@ -0,0 +1,3 @@ +# Inherits OWNERS from parent directory, plus the following + +vichang@google.com diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java index 80c8ec852146..5b5c8548f281 100644 --- a/core/java/android/text/format/Time.java +++ b/core/java/android/text/format/Time.java @@ -361,7 +361,7 @@ public class Time { */ @Override public String toString() { - // toString() uses its own TimeCalculator rather than the shared one. Otherwise crazy stuff + // toString() uses its own TimeCalculator rather than the shared one. Otherwise weird stuff // happens during debugging when the debugger calls toString(). TimeCalculator calculator = new TimeCalculator(this.timezone); calculator.copyFieldsFromTime(this); diff --git a/core/java/android/text/format/TimeFormatter.java b/core/java/android/text/format/TimeFormatter.java index 9393f36d1b6f..c71dfbbafd40 100644 --- a/core/java/android/text/format/TimeFormatter.java +++ b/core/java/android/text/format/TimeFormatter.java @@ -22,11 +22,10 @@ package android.text.format; import android.content.res.Resources; import android.icu.text.DateFormatSymbols; +import android.icu.text.DecimalFormatSymbols; import com.android.i18n.timezone.ZoneInfoData; -import libcore.icu.LocaleData; - import java.nio.CharBuffer; import java.time.Instant; import java.time.LocalDateTime; @@ -53,17 +52,17 @@ class TimeFormatter { private static final int DAYSPERNYEAR = 365; /** - * The Locale for which the cached LocaleData and formats have been loaded. + * The Locale for which the cached symbols and formats have been loaded. */ private static Locale sLocale; private static DateFormatSymbols sDateFormatSymbols; - private static LocaleData sLocaleData; + private static DecimalFormatSymbols sDecimalFormatSymbols; private static String sTimeOnlyFormat; private static String sDateOnlyFormat; private static String sDateTimeFormat; private final DateFormatSymbols dateFormatSymbols; - private final LocaleData localeData; + private final DecimalFormatSymbols decimalFormatSymbols; private final String dateTimeFormat; private final String timeOnlyFormat; private final String dateOnlyFormat; @@ -78,7 +77,7 @@ class TimeFormatter { if (sLocale == null || !(locale.equals(sLocale))) { sLocale = locale; sDateFormatSymbols = DateFormat.getIcuDateFormatSymbols(locale); - sLocaleData = LocaleData.get(locale); + sDecimalFormatSymbols = DecimalFormatSymbols.getInstance(locale); Resources r = Resources.getSystem(); sTimeOnlyFormat = r.getString(com.android.internal.R.string.time_of_day); @@ -87,10 +86,10 @@ class TimeFormatter { } this.dateFormatSymbols = sDateFormatSymbols; + this.decimalFormatSymbols = sDecimalFormatSymbols; this.dateTimeFormat = sDateTimeFormat; this.timeOnlyFormat = sTimeOnlyFormat; this.dateOnlyFormat = sDateOnlyFormat; - localeData = sLocaleData; } } @@ -172,12 +171,12 @@ class TimeFormatter { } private String localizeDigits(String s) { - if (localeData.zeroDigit == '0') { + if (decimalFormatSymbols.getZeroDigit() == '0') { return s; } int length = s.length(); - int offsetToLocalizedDigits = localeData.zeroDigit - '0'; + int offsetToLocalizedDigits = decimalFormatSymbols.getZeroDigit() - '0'; StringBuilder result = new StringBuilder(length); for (int i = 0; i < length; ++i) { char ch = s.charAt(i); @@ -220,35 +219,44 @@ class TimeFormatter { char currentChar = formatBuffer.get(formatBuffer.position()); switch (currentChar) { case 'A': - modifyAndAppend((wallTime.getWeekDay() < 0 - || wallTime.getWeekDay() >= DAYSPERWEEK) - ? "?" : localeData.longWeekdayNames[wallTime.getWeekDay() + 1], + modifyAndAppend( + (wallTime.getWeekDay() < 0 || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" + : dateFormatSymbols.getWeekdays(DateFormatSymbols.FORMAT, + DateFormatSymbols.WIDE)[wallTime.getWeekDay() + 1], modifier); return false; case 'a': - modifyAndAppend((wallTime.getWeekDay() < 0 - || wallTime.getWeekDay() >= DAYSPERWEEK) - ? "?" : localeData.shortWeekdayNames[wallTime.getWeekDay() + 1], + modifyAndAppend( + (wallTime.getWeekDay() < 0 || wallTime.getWeekDay() >= DAYSPERWEEK) + ? "?" + : dateFormatSymbols.getWeekdays(DateFormatSymbols.FORMAT, + DateFormatSymbols.ABBREVIATED)[wallTime.getWeekDay() + 1], modifier); return false; case 'B': if (modifier == '-') { - modifyAndAppend((wallTime.getMonth() < 0 - || wallTime.getMonth() >= MONSPERYEAR) - ? "?" - : localeData.longStandAloneMonthNames[wallTime.getMonth()], + modifyAndAppend( + (wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR) + ? "?" + : dateFormatSymbols.getMonths(DateFormatSymbols.STANDALONE, + DateFormatSymbols.WIDE)[wallTime.getMonth()], modifier); } else { - modifyAndAppend((wallTime.getMonth() < 0 - || wallTime.getMonth() >= MONSPERYEAR) - ? "?" : localeData.longMonthNames[wallTime.getMonth()], + modifyAndAppend( + (wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR) + ? "?" + : dateFormatSymbols.getMonths(DateFormatSymbols.FORMAT, + DateFormatSymbols.WIDE)[wallTime.getMonth()], modifier); } return false; case 'b': case 'h': modifyAndAppend((wallTime.getMonth() < 0 || wallTime.getMonth() >= MONSPERYEAR) - ? "?" : localeData.shortMonthNames[wallTime.getMonth()], + ? "?" + : dateFormatSymbols.getMonths(DateFormatSymbols.FORMAT, + DateFormatSymbols.ABBREVIATED)[wallTime.getMonth()], modifier); return false; case 'C': diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index 064bc6947fc4..713cfb48c95f 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -311,9 +311,6 @@ public class FocusFinder { } final int count = focusables.size(); - if (count < 2) { - return null; - } switch (direction) { case View.FOCUS_FORWARD: return getNextFocusable(focused, focusables, count); @@ -376,29 +373,29 @@ public class FocusFinder { } private static View getNextFocusable(View focused, ArrayList<View> focusables, int count) { - if (count < 2) { - return null; - } if (focused != null) { int position = focusables.lastIndexOf(focused); if (position >= 0 && position + 1 < count) { return focusables.get(position + 1); } } - return focusables.get(0); + if (!focusables.isEmpty()) { + return focusables.get(0); + } + return null; } private static View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) { - if (count < 2) { - return null; - } if (focused != null) { int position = focusables.indexOf(focused); if (position > 0) { return focusables.get(position - 1); } } - return focusables.get(count - 1); + if (!focusables.isEmpty()) { + return focusables.get(count - 1); + } + return null; } private static View getNextKeyboardNavigationCluster( diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index fbfeda6f0bcc..b398cf6c9cb3 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -727,16 +727,22 @@ public final class AutofillManager { mState = savedInstanceState.getInt(STATE_TAG, STATE_UNKNOWN); if (mSessionId != NO_SESSION) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); final AutofillClient client = getClient(); if (client != null) { final SyncResultReceiver receiver = new SyncResultReceiver( SYNC_CALLS_TIMEOUT_MS); try { - mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(), - mServiceClient.asBinder(), receiver); - final boolean sessionWasRestored = receiver.getIntResult() == 1; + boolean sessionWasRestored = false; + if (clientAdded) { + mService.restoreSession(mSessionId, + client.autofillClientGetActivityToken(), + mServiceClient.asBinder(), receiver); + sessionWasRestored = receiver.getIntResult() == 1; + } else { + Log.w(TAG, "No service client for session " + mSessionId); + } if (!sessionWasRestored) { Log.w(TAG, "Session " + mSessionId + " could not be restored"); @@ -850,8 +856,8 @@ public final class AutofillManager { if (isDisabledByServiceLocked()) { return false; } - ensureServiceClientAddedIfNeededLocked(); - return mEnabled; + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + return clientAdded ? mEnabled : false; } } @@ -1007,7 +1013,12 @@ public final class AutofillManager { AutofillCallback callback = null; - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + + if (!clientAdded) { + if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); + return callback; + } if (!mEnabled && !mEnabledForAugmentedAutofillOnly) { if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): disabled"); @@ -1060,9 +1071,10 @@ public final class AutofillManager { @GuardedBy("mLock") void notifyViewExitedLocked(@NonNull View view) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); - if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) { + if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) + && isActiveLocked()) { // dont notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = view.getAutofillId(); @@ -1178,7 +1190,12 @@ public final class AutofillManager { AutofillCallback callback = null; if (shouldIgnoreViewEnteredLocked(id, flags)) return callback; - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + + if (!clientAdded) { + if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); + return callback; + } if (!mEnabled && !mEnabledForAugmentedAutofillOnly) { if (sVerbose) { @@ -1241,9 +1258,10 @@ public final class AutofillManager { @GuardedBy("mLock") private void notifyViewExitedLocked(@NonNull View view, int virtualId) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); - if ((mEnabled || mEnabledForAugmentedAutofillOnly) && isActiveLocked()) { + if (clientAdded && (mEnabled || mEnabledForAugmentedAutofillOnly) + && isActiveLocked()) { // don't notify exited when Activity is already in background if (!isClientDisablingEnterExitEvent()) { final AutofillId id = getAutofillId(view, virtualId); @@ -1905,11 +1923,16 @@ public final class AutofillManager { } } + /** + * Tries to add AutofillManagerClient to service if it does not been added. Returns {@code true} + * if the AutofillManagerClient is added successfully or is already added. Otherwise, + * returns {@code false}. + */ @GuardedBy("mLock") - private void ensureServiceClientAddedIfNeededLocked() { + private boolean tryAddServiceClientIfNeededLocked() { final AutofillClient client = getClient(); if (client == null) { - return; + return false; } if (mServiceClient == null) { @@ -1924,7 +1947,10 @@ public final class AutofillManager { flags = receiver.getIntResult(); } catch (SyncResultReceiver.TimeoutException e) { Log.w(TAG, "Failed to initialize autofill: " + e); - return; + // Reset the states initialized above. + mService.removeClient(mServiceClient, userId); + mServiceClient = null; + return false; } mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0; sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0; @@ -1949,6 +1975,7 @@ public final class AutofillManager { throw e.rethrowFromSystemServer(); } } + return true; } @GuardedBy("mLock") @@ -1962,12 +1989,13 @@ public final class AutofillManager { && view.isLaidOut() && view.isVisibleToUser()) { - ensureServiceClientAddedIfNeededLocked(); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(); if (sVerbose) { - Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled); + Log.v(TAG, "startAutofillIfNeededLocked(): enabled=" + mEnabled + " mServiceClient=" + + mServiceClient); } - if (mEnabled && !isClientDisablingEnterExitEvent()) { + if (clientAdded && mEnabled && !isClientDisablingEnterExitEvent()) { final AutofillId id = view.getAutofillId(); final AutofillValue value = view.getAutofillValue(); // Starts new session. @@ -2692,6 +2720,7 @@ public final class AutofillManager { pw.print(pfx); pw.print("sessionId: "); pw.println(mSessionId); pw.print(pfx); pw.print("state: "); pw.println(getStateAsStringLocked()); pw.print(pfx); pw.print("context: "); pw.println(mContext); + pw.print(pfx); pw.print("service client: "); pw.println(mServiceClient); final AutofillClient client = getClient(); if (client != null) { pw.print(pfx); pw.print("client: "); pw.print(client); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 1c75232dc15c..052bca57d77c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1511,7 +1511,7 @@ public class WebView extends AbsoluteLayout * * @param hosts the list of hosts * @param callback will be called with {@code true} if hosts are successfully added to the - * whitelist. It will be called with {@code false} if any hosts are malformed. The callback + * allowlist. It will be called with {@code false} if any hosts are malformed. The callback * will be run on the UI thread */ public static void setSafeBrowsingWhitelist(@NonNull List<String> hosts, diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index 150fa88a36e3..7b6e1a370479 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -173,8 +173,9 @@ public class WebViewClient { * when accessing private data or the view system. * * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe - * Browsing checks. If this is undesired, whitelist the URL with {@link - * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}. + * Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist} + * to skip Safe Browsing checks for that host or dismiss the warning in {@link + * #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}. * * @param view The {@link android.webkit.WebView} that is requesting the * resource. @@ -211,8 +212,9 @@ public class WebViewClient { * when accessing private data or the view system. * * <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe - * Browsing checks. If this is undesired, whitelist the URL with {@link - * WebView#setSafeBrowsingWhitelist} or ignore the warning with {@link #onSafeBrowsingHit}. + * Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist} + * to skip Safe Browsing checks for that host or dismiss the warning in {@link + * #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}. * * @param view The {@link android.webkit.WebView} that is requesting the * resource. diff --git a/core/java/android/widget/CalendarViewLegacyDelegate.java b/core/java/android/widget/CalendarViewLegacyDelegate.java index 1b899dbf6d03..33e64f4d37e9 100644 --- a/core/java/android/widget/CalendarViewLegacyDelegate.java +++ b/core/java/android/widget/CalendarViewLegacyDelegate.java @@ -38,8 +38,6 @@ import android.view.ViewGroup; import com.android.internal.R; -import libcore.icu.LocaleData; - import java.util.Locale; /** @@ -264,7 +262,7 @@ class CalendarViewLegacyDelegate extends CalendarView.AbstractCalendarViewDelega mShowWeekNumber = a.getBoolean(R.styleable.CalendarView_showWeekNumber, DEFAULT_SHOW_WEEK_NUMBER); mFirstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); + Calendar.getInstance().getFirstDayOfWeek()); final String minDate = a.getString(R.styleable.CalendarView_minDate); if (!CalendarView.parseDate(minDate, mMinDate)) { CalendarView.parseDate(DEFAULT_MIN_DATE, mMinDate); diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java index 67fef13d23f2..7de2bd10482f 100644 --- a/core/java/android/widget/DayPickerView.java +++ b/core/java/android/widget/DayPickerView.java @@ -33,10 +33,6 @@ import com.android.internal.R; import com.android.internal.widget.ViewPager; import com.android.internal.widget.ViewPager.OnPageChangeListener; -import libcore.icu.LocaleData; - -import java.util.Locale; - class DayPickerView extends ViewGroup { private static final int DEFAULT_LAYOUT = R.layout.day_picker_content_material; private static final int DEFAULT_START_YEAR = 1900; @@ -86,7 +82,7 @@ class DayPickerView extends ViewGroup { attrs, a, defStyleAttr, defStyleRes); final int firstDayOfWeek = a.getInt(R.styleable.CalendarView_firstDayOfWeek, - LocaleData.get(Locale.getDefault()).firstDayOfWeek); + Calendar.getInstance().getFirstDayOfWeek()); final String minDate = a.getString(R.styleable.CalendarView_minDate); final String maxDate = a.getString(R.styleable.CalendarView_maxDate); diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 23bbe69afafb..89206fda39f3 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -1142,7 +1142,7 @@ public final class Magnifier { bitmapRenderNode.setOutline(outline); bitmapRenderNode.setClipToOutline(true); - // Create a dummy draw, which will be replaced later with real drawing. + // Create a placeholder draw, which will be replaced later with real drawing. final RecordingCanvas canvas = bitmapRenderNode.beginRecording( mContentWidth, mContentHeight); try { diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index baaf2a763487..3b482a8b2d54 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -34,6 +34,7 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.icu.text.DecimalFormatSymbols; import android.os.Build; import android.os.Bundle; import android.text.InputFilter; @@ -61,8 +62,6 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.R; -import libcore.icu.LocaleData; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -209,7 +208,7 @@ public class NumberPicker extends LinearLayout { } private static char getZeroDigit(Locale locale) { - return LocaleData.get(locale).zeroDigit; + return DecimalFormatSymbols.getInstance(locale).getZeroDigit(); } private java.util.Formatter createFormatter(Locale locale) { diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java index 61c77bc2f90e..695a253a04e0 100644 --- a/core/java/android/widget/SimpleMonthView.java +++ b/core/java/android/widget/SimpleMonthView.java @@ -27,6 +27,7 @@ import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Typeface; +import android.icu.text.DateFormatSymbols; import android.icu.text.DisplayContext; import android.icu.text.RelativeDateTimeFormatter; import android.icu.text.SimpleDateFormat; @@ -50,8 +51,6 @@ import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.R; import com.android.internal.widget.ExploreByTouchHelper; -import libcore.icu.LocaleData; - import java.text.NumberFormat; import java.util.Locale; @@ -194,7 +193,8 @@ class SimpleMonthView extends View { private void updateDayOfWeekLabels() { // Use tiny (e.g. single-character) weekday names from ICU. The indices // for this list correspond to Calendar days, e.g. SUNDAY is index 1. - final String[] tinyWeekdayNames = LocaleData.get(mLocale).tinyWeekdayNames; + final String[] tinyWeekdayNames = DateFormatSymbols.getInstance(mLocale) + .getWeekdays(DateFormatSymbols.FORMAT, DateFormatSymbols.NARROW); for (int i = 0; i < DAYS_IN_WEEK; i++) { mDayOfWeekLabels[i] = tinyWeekdayNames[(mWeekStart + i - 1) % DAYS_IN_WEEK + 1]; } diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java index 6432438b6630..95c0e8658c57 100644 --- a/core/java/android/widget/TextClock.java +++ b/core/java/android/widget/TextClock.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.TypedArray; import android.database.ContentObserver; +import android.icu.text.DateTimePatternGenerator; import android.net.Uri; import android.os.Handler; import android.os.SystemClock; @@ -43,8 +44,6 @@ import android.view.inspector.InspectableProperty; import com.android.internal.R; -import libcore.icu.LocaleData; - import java.util.Calendar; import java.util.TimeZone; @@ -262,14 +261,11 @@ public class TextClock extends TextView { } private void init() { - if (mFormat12 == null || mFormat24 == null) { - LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); - if (mFormat12 == null) { - mFormat12 = ld.timeFormat_hm; - } - if (mFormat24 == null) { - mFormat24 = ld.timeFormat_Hm; - } + if (mFormat12 == null) { + mFormat12 = getBestDateTimePattern("hm"); + } + if (mFormat24 == null) { + mFormat24 = getBestDateTimePattern("Hm"); } createTime(mTimeZone); @@ -510,13 +506,11 @@ public class TextClock extends TextView { private void chooseFormat() { final boolean format24Requested = is24HourModeEnabled(); - LocaleData ld = LocaleData.get(getContext().getResources().getConfiguration().locale); - if (format24Requested) { - mFormat = abc(mFormat24, mFormat12, ld.timeFormat_Hm); + mFormat = abc(mFormat24, mFormat12, getBestDateTimePattern("Hm")); mDescFormat = abc(mDescFormat24, mDescFormat12, mFormat); } else { - mFormat = abc(mFormat12, mFormat24, ld.timeFormat_hm); + mFormat = abc(mFormat12, mFormat24, getBestDateTimePattern("hm")); mDescFormat = abc(mDescFormat12, mDescFormat24, mFormat); } @@ -529,6 +523,12 @@ public class TextClock extends TextView { } } + private String getBestDateTimePattern(String skeleton) { + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance( + getContext().getResources().getConfiguration().locale); + return dtpg.getBestPattern(skeleton); + } + /** * Returns a if not null, else return b if not null, else return c. */ diff --git a/core/java/com/android/internal/app/ProcessMap.java b/core/java/com/android/internal/app/ProcessMap.java index 81036f7ecba8..719c79b2540f 100644 --- a/core/java/com/android/internal/app/ProcessMap.java +++ b/core/java/com/android/internal/app/ProcessMap.java @@ -58,4 +58,8 @@ public class ProcessMap<E> { public int size() { return mMap.size(); } + + public void clear() { + mMap.clear(); + } } diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java index d238d0eb916d..ea3d2de13ce6 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java @@ -120,6 +120,13 @@ public final class SystemUiDeviceConfigFlags { */ public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days"; + // Flag related to Privacy Indicators + + /** + * Whether the Permissions Hub is showing. + */ + public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_2_enabled"; + // Flags related to Assistant /** diff --git a/core/java/com/android/internal/listeners/ListenerExecutor.java b/core/java/com/android/internal/listeners/ListenerExecutor.java index e78e32b829b3..9979e6056f50 100644 --- a/core/java/com/android/internal/listeners/ListenerExecutor.java +++ b/core/java/com/android/internal/listeners/ListenerExecutor.java @@ -40,7 +40,7 @@ public interface ListenerExecutor { /** * Called before this operation is to be run. Some operations may be canceled before they * are run, in which case this method may not be called. {@link #onPostExecute(boolean)} - * will only be run if this method was run. + * will only be run if this method was run. This callback is invoked on the calling thread. */ default void onPreExecute() {} @@ -49,7 +49,7 @@ public interface ListenerExecutor { * RuntimeException, which will propagate normally. Implementations of * {@link ListenerExecutor} have the option to override * {@link ListenerExecutor#onOperationFailure(ListenerOperation, Exception)} instead to - * intercept failures at the class level. + * intercept failures at the class level. This callback is invoked on the executor thread. */ default void onFailure(Exception e) { // implementations should handle any exceptions that may be thrown @@ -59,21 +59,24 @@ public interface ListenerExecutor { /** * Called after the operation is run. This method will always be called if * {@link #onPreExecute()} is called. Success implies that the operation was run to - * completion with no failures. + * completion with no failures. This callback may be invoked on the calling thread or + * executor thread. */ default void onPostExecute(boolean success) {} /** * Called after this operation is complete (which does not imply that it was necessarily * run). Will always be called once per operation, no matter if the operation was run or - * not. Success implies that the operation was run to completion with no failures. + * not. Success implies that the operation was run to completion with no failures. This + * callback may be invoked on the calling thread or executor thread. */ default void onComplete(boolean success) {} } /** * May be override to handle operation failures at a class level. Will not be invoked in the - * event of a RuntimeException, which will propagate normally. + * event of a RuntimeException, which will propagate normally. This callback is invoked on the + * executor thread. */ default <TListener> void onOperationFailure(ListenerOperation<TListener> operation, Exception exception) { @@ -83,7 +86,8 @@ public interface ListenerExecutor { /** * Executes the given listener operation on the given executor, using the provided listener * supplier. If the supplier returns a null value, or a value during the operation that does not - * match the value prior to the operation, then the operation is considered canceled. + * match the value prior to the operation, then the operation is considered canceled. If a null + * operation is supplied, nothing happens. */ default <TListener> void executeSafely(Executor executor, Supplier<TListener> listenerSupplier, @Nullable ListenerOperation<TListener> operation) { diff --git a/core/java/com/android/internal/os/ClassLoaderFactory.java b/core/java/com/android/internal/os/ClassLoaderFactory.java index a18943c264f5..f83c5bdc4e28 100644 --- a/core/java/com/android/internal/os/ClassLoaderFactory.java +++ b/core/java/com/android/internal/os/ClassLoaderFactory.java @@ -101,7 +101,7 @@ public class ClassLoaderFactory { String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared, String classLoaderName) { return createClassLoader(dexPath, librarySearchPath, libraryPermittedPath, - parent, targetSdkVersion, isNamespaceShared, classLoaderName, null); + parent, targetSdkVersion, isNamespaceShared, classLoaderName, null, null); } @@ -111,18 +111,24 @@ public class ClassLoaderFactory { public static ClassLoader createClassLoader(String dexPath, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, int targetSdkVersion, boolean isNamespaceShared, String classLoaderName, - List<ClassLoader> sharedLibraries) { + List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) { final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent, classLoaderName, sharedLibraries); + String sonameList = ""; + if (nativeSharedLibraries != null) { + sonameList = String.join(":", nativeSharedLibraries); + } + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "createClassloaderNamespace"); String errorMessage = createClassloaderNamespace(classLoader, targetSdkVersion, librarySearchPath, libraryPermittedPath, isNamespaceShared, - dexPath); + dexPath, + sonameList); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); if (errorMessage != null) { @@ -139,5 +145,6 @@ public class ClassLoaderFactory { String librarySearchPath, String libraryPermittedPath, boolean isNamespaceShared, - String dexPath); + String dexPath, + String sonameList); } diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java index 0eb3981ed598..c6a1153c747f 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -49,10 +49,12 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -104,11 +106,17 @@ public class SystemConfig { public final String name; public final String filename; public final String[] dependencies; + public final boolean isNative; SharedLibraryEntry(String name, String filename, String[] dependencies) { + this(name, filename, dependencies, false /* isNative */); + } + + SharedLibraryEntry(String name, String filename, String[] dependencies, boolean isNative) { this.name = name; this.filename = filename; this.dependencies = dependencies; + this.isNative = isNative; } } @@ -170,12 +178,6 @@ public class SystemConfig { // URL-handling state upon factory reset. final ArraySet<String> mLinkedApps = new ArraySet<>(); - // These are the packages that are whitelisted to be able to run as system user - final ArraySet<String> mSystemUserWhitelistedApps = new ArraySet<>(); - - // These are the packages that should not run under system user - final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>(); - // These are the components that are enabled by default as VR mode listener services. final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>(); @@ -309,14 +311,6 @@ public class SystemConfig { return mLinkedApps; } - public ArraySet<String> getSystemUserWhitelistedApps() { - return mSystemUserWhitelistedApps; - } - - public ArraySet<String> getSystemUserBlacklistedApps() { - return mSystemUserBlacklistedApps; - } - public ArraySet<String> getHiddenApiWhitelistedApps() { return mHiddenApiPackageWhitelist; } @@ -457,6 +451,7 @@ public class SystemConfig { log.traceBegin("readAllPermissions"); try { readAllPermissions(); + readPublicNativeLibrariesList(); } finally { log.traceEnd(); } @@ -895,34 +890,6 @@ public class SystemConfig { } XmlUtils.skipCurrentTag(parser); } break; - case "system-user-whitelisted-app": { - if (allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<" + name + "> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mSystemUserWhitelistedApps.add(pkgname); - } - } else { - logNotAllowedInPartition(name, permFile, parser); - } - XmlUtils.skipCurrentTag(parser); - } break; - case "system-user-blacklisted-app": { - if (allowAppConfigs) { - String pkgname = parser.getAttributeValue(null, "package"); - if (pkgname == null) { - Slog.w(TAG, "<" + name + "> without package in " - + permFile + " at " + parser.getPositionDescription()); - } else { - mSystemUserBlacklistedApps.add(pkgname); - } - } else { - logNotAllowedInPartition(name, permFile, parser); - } - XmlUtils.skipCurrentTag(parser); - } break; case "default-enabled-vr-app": { if (allowAppConfigs) { String pkgname = parser.getAttributeValue(null, "package"); @@ -1513,6 +1480,37 @@ public class SystemConfig { } } + private void readPublicNativeLibrariesList() { + readPublicLibrariesListFile(new File("/vendor/etc/public.libraries.txt")); + String[] dirs = {"/system/etc", "/system_ext/etc", "/product/etc"}; + for (String dir : dirs) { + for (File f : (new File(dir)).listFiles()) { + String name = f.getName(); + if (name.startsWith("public.libraries-") && name.endsWith(".txt")) { + readPublicLibrariesListFile(f); + } + } + } + } + + private void readPublicLibrariesListFile(File listFile) { + try (BufferedReader br = new BufferedReader(new FileReader(listFile))) { + String line; + while ((line = br.readLine()) != null) { + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + // Line format is <soname> [abi]. We take the soname part. + String soname = line.trim().split(" ")[0]; + SharedLibraryEntry entry = new SharedLibraryEntry( + soname, soname, new String[0], true); + mSharedLibraries.put(entry.name, entry); + } + } catch (IOException e) { + Slog.w(TAG, "Failed to read public libraries file " + listFile, e); + } + } + private static boolean isSystemProcess() { return Process.myUid() == Process.SYSTEM_UID; } diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 3da2fa26ffe6..d6e8531fa6ca 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -853,9 +853,8 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz) } cfg_state = CONFIG_UNKNOWN; if (cfg_state == CONFIG_UNKNOWN) { - auto runtime_info = vintf::VintfObject::GetInstance() - ->getRuntimeInfo(false /* skip cache */, - vintf::RuntimeInfo::FetchFlag::CONFIG_GZ); + auto runtime_info = vintf::VintfObject::GetInstance()->getRuntimeInfo( + vintf::RuntimeInfo::FetchFlag::CONFIG_GZ); CHECK(runtime_info != nullptr) << "Kernel configs cannot be fetched. b/151092221"; const std::map<std::string, std::string>& configs = runtime_info->kernelConfigs(); std::map<std::string, std::string>::const_iterator it = configs.find("CONFIG_VMAP_STACK"); diff --git a/core/jni/android_os_VintfRuntimeInfo.cpp b/core/jni/android_os_VintfRuntimeInfo.cpp index 9379ea6dcd10..b0271b9e92af 100644 --- a/core/jni/android_os_VintfRuntimeInfo.cpp +++ b/core/jni/android_os_VintfRuntimeInfo.cpp @@ -29,14 +29,12 @@ namespace android { using vintf::RuntimeInfo; using vintf::VintfObject; -#define MAP_STRING_METHOD(javaMethod, cppString, flags) \ - static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) \ - { \ - std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo( \ - false /* skipCache */, flags); \ - if (info == nullptr) return nullptr; \ - return env->NewStringUTF((cppString).c_str()); \ - } \ +#define MAP_STRING_METHOD(javaMethod, cppString, flags) \ + static jstring android_os_VintfRuntimeInfo_##javaMethod(JNIEnv* env, jclass clazz) { \ + std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo(flags); \ + if (info == nullptr) return nullptr; \ + return env->NewStringUTF((cppString).c_str()); \ + } MAP_STRING_METHOD(getCpuInfo, info->cpuInfo(), RuntimeInfo::FetchFlag::CPU_INFO); MAP_STRING_METHOD(getOsName, info->osName(), RuntimeInfo::FetchFlag::CPU_VERSION); @@ -54,8 +52,8 @@ MAP_STRING_METHOD(getBootVbmetaAvbVersion, vintf::to_string(info->bootVbmetaAvbV static jlong android_os_VintfRuntimeInfo_getKernelSepolicyVersion(JNIEnv *env, jclass clazz) { - std::shared_ptr<const RuntimeInfo> info = VintfObject::GetRuntimeInfo( - false /* skipCache */, RuntimeInfo::FetchFlag::POLICYVERS); + std::shared_ptr<const RuntimeInfo> info = + VintfObject::GetRuntimeInfo(RuntimeInfo::FetchFlag::POLICYVERS); if (info == nullptr) return 0; return static_cast<jlong>(info->kernelSepolicyVersion()); } diff --git a/core/jni/com_android_internal_os_ClassLoaderFactory.cpp b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp index f8d41e4bef54..59c413ff58a6 100644 --- a/core/jni/com_android_internal_os_ClassLoaderFactory.cpp +++ b/core/jni/com_android_internal_os_ClassLoaderFactory.cpp @@ -28,16 +28,19 @@ static jstring createClassloaderNamespace_native(JNIEnv* env, jstring librarySearchPath, jstring libraryPermittedPath, jboolean isShared, - jstring dexPath) { + jstring dexPath, + jstring sonameList) { return android::CreateClassLoaderNamespace(env, targetSdkVersion, classLoader, isShared == JNI_TRUE, dexPath, - librarySearchPath, libraryPermittedPath); + librarySearchPath, + libraryPermittedPath, + sonameList); } static const JNINativeMethod g_methods[] = { { "createClassloaderNamespace", - "(Ljava/lang/ClassLoader;ILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;)Ljava/lang/String;", + "(Ljava/lang/ClassLoader;ILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;", reinterpret_cast<void*>(createClassloaderNamespace_native) }, }; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index ef50fc8e2b6e..fe290f3e97e8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5034,6 +5034,11 @@ <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS" android:protectionLevel="signature|appPredictor" /> + <!-- @hide @TestApi Allows apps to reset the state of {@link com.android.server.am.AppErrors}. + <p>CTS tests will use UiAutomation.adoptShellPermissionIdentity() to gain access. --> + <permission android:name="android.permission.RESET_APP_ERRORS" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index 3b61c367e1bf..03c682fd74af 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Druk kieslys om oop te sluit of maak noodoproep."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Druk kieslys om oop te maak."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Teken patroon om te ontsluit"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Noodgeval"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Keer terug na oproep"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Reg!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Probeer weer"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 9ce214fdf3ab..f80da896d8fa 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ለመክፈት ምናሌ ተጫንወይም የአደጋ ጊዜ ጥሪ አድርግ።"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ለመክፈት ምናሌ ተጫን"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ለመክፈት ስርዓተ ጥለት ሳል"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ድንገተኛ አደጋ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ወደ ጥሪ ተመለስ"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ትክክል!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"እንደገና ሞክር"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index e80720d9a2b8..41e3e26afc2c 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -844,7 +844,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"اضغط على \"القائمة\" لإلغاء التأمين أو إجراء اتصال بالطوارئ."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"اضغط على \"القائمة\" لإلغاء التأمين."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"رسم نقش لإلغاء التأمين"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"الطوارئ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"العودة إلى الاتصال"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"صحيح!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"أعد المحاولة"</string> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index 881a1278666c..c6c8bb502ba8 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"আনলক কৰিবলৈ বা জৰুৰীকালীন কল কৰিবলৈ মেনু টিপক।"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"আনলক কৰিবলৈ মেনু টিপক।"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"আনলক কৰিবলৈ আর্হি আঁকক"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"জৰুৰীকালীন"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"কললৈ উভতি যাওক"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"শুদ্ধ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"আকৌ চেষ্টা কৰক"</string> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index 6408ef629b36..f0ff88365173 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Təcili zəng kilidini açmaq və ya yerləşdirmək üçün Menyu düyməsinə basın."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Kilidi açmaq üçün Menyu düyməsinə basın."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Kilidi açmaq üçün model çəkin"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Təcili"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Zəngə qayıt"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Düzdür!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Bir də cəhd edin"</string> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 2bcf9095cb04..8319da7ca7ee 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pritisnite „Meni“ da biste otključali telefon ili uputite hitan poziv."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pritisnite „Meni“ za otključavanje."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Unesite šablon za otključavanje"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hitne službe"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Nazad na poziv"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Tačno!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Probajte ponovo"</string> @@ -1145,7 +1146,7 @@ <string name="capital_off" msgid="7443704171014626777">"NE"</string> <string name="checked" msgid="9179896827054513119">"označeno je"</string> <string name="not_checked" msgid="7972320087569023342">"nije označeno"</string> - <string name="whichApplication" msgid="5432266899591255759">"Dovršavanje radnje pomoću"</string> + <string name="whichApplication" msgid="5432266899591255759">"Dovrši radnju preko"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Završite radnju pomoću aplikacije %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Završi radnju"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Otvorite pomoću"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index ed781fb25698..3540516fd9f3 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Націсніце \"Меню\", каб разблакаваць, або зрабіце экстраны выклік."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Націсніце \"Меню\", каб разблакаваць."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Намалюйце камбінацыю разблакоўкі, каб разблакаваць"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Экстранны выклік"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Вярнуцца да выкліку"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Правільна!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Паспрабуйце яшчэ раз"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index f089d62707b5..6643bcfa822c 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Натиснете „Меню“, за да отключите или да извършите спешно обаждане."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Натиснете „Меню“, за да отключите."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Нарисувайте фигура, за да отключите"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Спешни случаи"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Назад към обаждането"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Правилно!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Опитайте отново"</string> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 1ac213f01d6b..cbec4ac51d09 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"আনলক করতে বা জরুরি কল করতে মেনু টিপুন৷"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"আনলক করতে মেনু টিপুন৷"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"আনলক করতে প্যাটার্ন আঁকুন"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"জরুরী"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"কলে ফিরুন"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"সঠিক!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"আবার চেষ্টা করুন"</string> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index f27441a667e7..87b02f553037 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pritisnite dugme Meni kako biste otključali uređaj ili obavili hitni poziv."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pritisnite dugme Meni za otključavanje uređaja."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Nacrtajte uzorak za otključavanje"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hitno"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Povratak na poziv"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Ispravno!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Pokušajte ponovo"</string> @@ -1145,8 +1146,8 @@ <string name="capital_off" msgid="7443704171014626777">"Isključeno"</string> <string name="checked" msgid="9179896827054513119">"označeno"</string> <string name="not_checked" msgid="7972320087569023342">"nije označeno"</string> - <string name="whichApplication" msgid="5432266899591255759">"Izvrši akciju koristeći"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"Dovršite akciju koristeći %1$s"</string> + <string name="whichApplication" msgid="5432266899591255759">"Završite radnju pomoću aplikacije"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"Završite radnju pomoću aplikacije %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Izvršiti akciju"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Otvori koristeći"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"Otvori koristeći %1$s"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 856176455150..c69ca37d0f1c 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Chcete-li odemknout telefon nebo provést tísňové volání, stiskněte Menu."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Telefon odemknete stisknutím tlačítka Menu."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Odblokujte pomocí gesta"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Stav nouze"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Zavolat zpět"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Správně!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Zkusit znovu"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 1b3dab2352f4..fb100773dfa6 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Tryk på Menu for at låse op eller foretage et nødopkald."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Tryk på Menu for at låse op."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Tegn oplåsningsmønster"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Nødsituation"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Tilbage til opkald"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Rigtigt!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Prøv igen"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 296200f8004c..4adcf0ae48ec 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Drücke die Menütaste, um das Telefon zu entsperren oder einen Notruf zu tätigen."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Zum Entsperren die Menütaste drücken"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Muster zum Entsperren zeichnen"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Notfall"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Zurück zum Anruf"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Korrekt!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Erneut versuchen"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index e790c9a071a9..23895f4fbf57 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Πατήστε \"Menu\" για ξεκλείδωμα ή για κλήση έκτακτης ανάγκης."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Πατήστε \"Μενού\" για ξεκλείδωμα."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Σχεδιασμός μοτίβου για ξεκλείδωμα"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Κλήση έκτακτης ανάγκης"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Επιστροφή στην κλήση"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Σωστό!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Προσπαθήστε ξανά"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 0f1566b10347..0484ccd8abaa 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Press Menu to unlock or place emergency call."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Press Menu to unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Draw pattern to unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Return to call"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correct!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Try again"</string> diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index 84b3ce1ffc22..9981c1212123 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Press Menu to unlock or place emergency call."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Press Menu to unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Draw pattern to unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Return to call"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correct!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Try again"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index d9a2bdba5f10..02cdf69a2cd8 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Press Menu to unlock or place emergency call."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Press Menu to unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Draw pattern to unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Return to call"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correct!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Try again"</string> diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index 1209f2bc4318..3b18aca951f7 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Press Menu to unlock or place emergency call."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Press Menu to unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Draw pattern to unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Return to call"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correct!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Try again"</string> diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 7f29fd463403..6503f7ce65a7 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -832,7 +832,7 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Press Menu to unlock or place emergency call."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Press Menu to unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Draw pattern to unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <string name="lockscreen_emergency_call" msgid="7549683825868928636">"Emergency call"</string> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Return to call"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correct!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Try again"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index f8ead2795a9a..7e8a89aa5a94 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Presiona el Menú para desbloquear o realizar una llamada de emergencia."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Presionar Menú para desbloquear."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Dibujar el patrón de desbloqueo"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergencia"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Regresar a llamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correcto"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Vuelve a intentarlo."</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 741c89f0e5d9..02aafa75945a 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pulsa la tecla de menú para desbloquear el teléfono o realizar una llamada de emergencia."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pulsa la tecla de menú para desbloquear la pantalla."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Dibujar patrón de desbloqueo"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergencia"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Volver a llamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correcto"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Vuelve a intentarlo"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index d724046c2a73..813629417a8e 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Vajutage avamiseks või hädaabikõne tegemiseks menüünuppu"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Vajutage avamiseks menüüklahvi."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Avamiseks joonistage muster"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hädaabi"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Kõne juurde tagasi"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Õige."</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Proovige uuesti"</string> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index 24d3eef619e8..1b88f2708782 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Desblokeatzeko edo larrialdi-deia egiteko, sakatu Menua."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Desblokeatzeko, sakatu Menua."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desblokeatzeko, marraztu eredua"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Larrialdi-deiak"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Itzuli deira"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Eredua zuzena da!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Saiatu berriro"</string> @@ -846,7 +847,7 @@ <string name="lockscreen_missing_sim_instructions" msgid="8473601862688263903">"Sartu SIM txartela."</string> <string name="lockscreen_missing_sim_instructions_long" msgid="3664999892038416334">"SIM txartela falta da edo ezin da irakurri. Sartu SIM txartel bat."</string> <string name="lockscreen_permanent_disabled_sim_message_short" msgid="3812893366715730539">"SIM txartela hondatuta dago."</string> - <string name="lockscreen_permanent_disabled_sim_instructions" msgid="4358929052509450807">"SIM txartela behin betiko desgaitu zaizu.\n Beste SIM txartel bat lortzeko, jarri zerbitzu-hornitzailearekin harremanetan."</string> + <string name="lockscreen_permanent_disabled_sim_instructions" msgid="4358929052509450807">"SIM txartela betiko desgaitu zaizu.\n Beste SIM txartel bat lortzeko, jarri zerbitzu-hornitzailearekin harremanetan."</string> <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Aurreko pista"</string> <string name="lockscreen_transport_next_description" msgid="2931509904881099919">"Hurrengo pista"</string> <string name="lockscreen_transport_pause_description" msgid="6705284702135372494">"Pausatu"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index b0b533858b3a..506a7ee79dcb 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"برای بازگشایی قفل یا انجام تماس اضطراری روی منو فشار دهید."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"برای بازگشایی قفل روی منو فشار دهید."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"الگو را بکشید تا قفل آن باز شود"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"اضطراری"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"بازگشت به تماس"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"صحیح است!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"دوباره امتحان کنید"</string> @@ -1125,8 +1126,8 @@ <string name="capital_off" msgid="7443704171014626777">"خاموش"</string> <string name="checked" msgid="9179896827054513119">"علامتزدهشده"</string> <string name="not_checked" msgid="7972320087569023342">"بدون علامت"</string> - <string name="whichApplication" msgid="5432266899591255759">"تکمیل عملکرد با استفاده از"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"تکمیل عملکرد با استفاده از %1$s"</string> + <string name="whichApplication" msgid="5432266899591255759">"تکمیل کنش بااستفاده از"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"تکمیل کنش بااستفاده از %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"تکمیل عملکرد"</string> <string name="whichViewApplication" msgid="5733194231473132945">"باز کردن با"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"باز کردن با %1$s"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 815daaa5d7ae..01db781a6ccd 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Poista lukitus tai soita hätäpuhelu painamalla Valikko-painiketta."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Poista lukitus painamalla Valikko-painiketta."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Poista lukitus piirtämällä kuvio"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hätäpuhelu"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Palaa puheluun"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Oikein!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Yritä uudelleen"</string> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index d3323d733824..f5adc779e082 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Appuyez sur \"Menu\" pour débloquer le téléphone ou appeler un numéro d\'urgence."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Appuyez sur \"Menu\" pour déverrouiller l\'appareil."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Dessinez un schéma pour déverrouiller le téléphone"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgence"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Retour à l\'appel"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"C\'est exact!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Réessayer"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 4a1646f8ba30..8880dc63428f 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Appuyez sur \"Menu\" pour déverrouiller le téléphone ou appeler un numéro d\'urgence"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Appuyez sur \"Menu\" pour déverrouiller le téléphone."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Dessinez un schéma pour déverrouiller le téléphone"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgences"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Retour à l\'appel"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Combinaison correcte !"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Veuillez réessayer."</string> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 54e17faf9b4a..dffca1c2abcd 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Preme Menú para desbloquear ou realizar unha chamada de emerxencia."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Preme Menú para desbloquear."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Crea o padrón de desbloqueo"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emerxencia"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Volver á chamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correcto!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Téntao de novo"</string> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index be17cfd03115..784e85f9a174 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"અનલૉક કરવા માટે અથવા કટોકટીનો કૉલ કરવા માટે મેનૂ દબાવો."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"અનલૉક કરવા માટે મેનૂ દબાવો."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"અનલૉક કરવા માટે પૅટર્ન દોરો."</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ઇમર્જન્સી"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"કૉલ પર પાછા ફરો"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"સાચું!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ફરી પ્રયાસ કરો"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 97118f8bffae..82a106295acb 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"लॉक खोलने के लिए मेन्यू दबाएं या आपातलकालीन कॉल करें."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"लॉक खोलने के लिए मेन्यू दबाएं."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"अनलॉक करने के लिए आकार आरेखित करें"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"आपातकाल"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"कॉल पर वापस लौटें"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"सही!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"फिर से कोशिश करें"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index a1b303ee7cd4..f1bcd398c65f 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pritisnite Izbornik za otključavanje ili pozivanje hitnih službi."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pritisnite Izbornik za otključavanje."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Iscrtajte uzorak za otključavanje"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Hitne službe"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Uzvrati poziv"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Ispravno!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Pokušajte ponovo"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index ff0d4709c970..3106371a2a33 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"A feloldáshoz vagy segélyhívás kezdeményezéséhez nyomja meg a Menü gombot."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"A feloldáshoz nyomja meg a Menü gombot."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Rajzolja le a mintát a feloldáshoz"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Segélyhívás"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Hívás folytatása"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Helyes!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Próbálja újra"</string> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 05c447d50f7b..85afc27bb14c 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Ապակողպելու կամ շտապ կանչ անելու համար սեղմեք «Ընտրացանկ»"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Ապակողպելու համար սեղմեք Ցանկը:"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Հավաքեք սխեման` ապակողպելու համար"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Շտապ կանչ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Վերադառնալ զանգին"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Ճիշտ է:"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Կրկին փորձեք"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index a6f68d171ec4..8ec1f50eaceb 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Tekan Menu untuk membuka atau melakukan panggilan darurat."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Tekan Menu untuk membuka."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Buat pola untuk membuka"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Darurat"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Kembali ke panggilan"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Perbaiki!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Coba lagi"</string> @@ -1125,7 +1126,7 @@ <string name="capital_off" msgid="7443704171014626777">"MATI"</string> <string name="checked" msgid="9179896827054513119">"dicentang"</string> <string name="not_checked" msgid="7972320087569023342">"tidak dicentang"</string> - <string name="whichApplication" msgid="5432266899591255759">"Tindakan lengkap menggunakan"</string> + <string name="whichApplication" msgid="5432266899591255759">"Selesaikan tindakan menggunakan"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Selesaikan tindakan menggunakan %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Selesaikan tindakan"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Buka dengan"</string> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 92f6f2488d18..1b0c2fe24521 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Ýttu á valmyndartakkann til að taka úr lás eða hringja neyðarsímtal."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Ýttu á valmyndartakkann til að taka úr lás."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Teiknaðu mynstur til að taka úr lás"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Neyðarsímtal"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Aftur í símtal"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Rétt!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Reyndu aftur"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 8f857fa32c73..d2bf62d94a40 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Premi Menu per sbloccare o effettuare chiamate di emergenza."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Premi Menu per sbloccare."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Traccia la sequenza di sblocco"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergenza"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Torna a chiamata"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Corretta."</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Riprova"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 27889fb9b512..f9674bd0db11 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"MENUキーでロック解除(または緊急通報)"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"MENUキーでロック解除"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"パターンを入力"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"緊急通報"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"通話に戻る"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"一致しました"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"もう一度お試しください"</string> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 6547185c4beb..3d1e9c799a4d 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"განბლოკვისთვის ან გადაუდებელი ზარისთვის დააჭირეთ მენიუს."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"განბლოკვისთვის დააჭირეთ მენიუს."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"განსაბლოკად დახატეთ ნიმუში"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"საგანგებო სამსახურები"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ზარზე დაბრუნება"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"სწორია!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"კიდევ სცადეთ"</string> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 3d53912ad62b..38a50abfe6e7 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Бекітпесін ашу үшін немесе төтенше қоңырауды табу үшін Мәзір тармағын басыңыз."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Ашу үшін Мәзір пернесін басыңыз."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Бекітпесін ашу үшін кескінді сызыңыз"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Төтенше жағдай"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Қоңырауға оралу"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Дұрыс!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Қайталап көріңіз"</string> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 760d256ca62c..4e646c5d2a07 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ចុចម៉ឺនុយ ដើម្បីដោះសោ ឬហៅពេលអាសន្ន។"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ចុចម៉ឺនុយ ដើម្បីដោះសោ។"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"គូរលំនាំ ដើម្បីដោះសោ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"បន្ទាន់"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ត្រឡប់ទៅការហៅ"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ត្រឹមត្រូវ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ព្យាយាមម្ដងទៀត"</string> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 6513481089ad..8d742156ca2c 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ ಇಲ್ಲವೇ ತುರ್ತು ಕರೆಯನ್ನು ಮಾಡಿ."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಮೆನು ಒತ್ತಿರಿ."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ಅನ್ಲಾಕ್ ಮಾಡಲು ಪ್ಯಾಟರ್ನ್ ಚಿತ್ರಿಸಿ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ತುರ್ತು"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ಕರೆಗೆ ಹಿಂತಿರುಗು"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ಸರಿಯಾಗಿದೆ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index db8d379c4083..5d960dcc0aa7 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"비상 전화를 걸거나 잠금해제하려면 메뉴를 누르세요."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"잠금해제하려면 메뉴를 누르세요."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"잠금해제를 위해 패턴 그리기"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"긴급 전화"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"통화로 돌아가기"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"맞습니다."</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"다시 시도"</string> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index c208396b278a..c97324f738b3 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Кулпусун ачып же Шашылыш чалуу аткаруу үчүн менюну басыңыз."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Бөгөттөн чыгаруу үчүн Менюну басыңыз."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Кулпуну ачуу үчүн, үлгүнү тартыңыз"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Тез жардам"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Чалууга кайтуу"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Туура!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Дагы аракет кылыңыз"</string> @@ -1125,7 +1126,7 @@ <string name="capital_off" msgid="7443704171014626777">"ӨЧҮК"</string> <string name="checked" msgid="9179896827054513119">"белгиленген"</string> <string name="not_checked" msgid="7972320087569023342">"белгилене элек"</string> - <string name="whichApplication" msgid="5432266899591255759">"Аракет колдонууну бүтүрүү"</string> + <string name="whichApplication" msgid="5432266899591255759">"Кайсынысын колдоносуз?"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$s аркылуу аракетти аягына чейин чыгаруу"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Аракетти аягына чыгаруу"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Төмөнкү менен ачуу"</string> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index f388cfaed0ca..c1b3fe473aed 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ກົດ ເມນູ ເພື່ອປົດລັອກ ຫຼື ໂທອອກຫາເບີສຸກເສີນ."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ກົດ \"ເມນູ\" ເພື່ອປົດລັອກ."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ແຕ້ມຮູບແບບເພື່ອປົດລັອກ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ສຸກເສີນ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ກັບໄປຫາການໂທ"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ຖືກຕ້ອງ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ລອງໃໝ່ອີກຄັ້ງ"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index d68cd8d3c822..fc5709c0e089 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Paspauskite „Meniu“, kad atrakintumėte ar skambintumėte pagalbos numeriu."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Paspauskite „Meniu“, jei norite atrakinti."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Nustatyti modelį, kad atrakintų"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Skambutis pagalbos numeriu"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"grįžti prie skambučio"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Teisingai!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Bandykite dar kartą"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 9462c227a3cf..d780b14ae11a 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Nospiediet Izvēlne, lai atbloķētu, vai veiciet ārkārtas zvanu."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Lai atbloķētu, nospiediet vienumu Izvēlne."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Zīmējiet kombināciju, lai atbloķētu."</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Ārkārtas situācija"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Atpakaļ pie zvana"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Pareizi!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Mēģināt vēlreiz"</string> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index aee1fca01cb1..b50c608190e4 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Притисни „Мени“ да се отклучи или да направи итен повик."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Притиснете „Мени“ за да се отклучи."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Употребете ја шемата за да се отклучи"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Итен случај"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Врати се на повик"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Точно!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Обидете се повторно"</string> @@ -1125,7 +1126,7 @@ <string name="capital_off" msgid="7443704171014626777">"ИСКЛУЧЕНО"</string> <string name="checked" msgid="9179896827054513119">"штиклирано"</string> <string name="not_checked" msgid="7972320087569023342">"не е штиклирано"</string> - <string name="whichApplication" msgid="5432266899591255759">"Заврши дејство со"</string> + <string name="whichApplication" msgid="5432266899591255759">"Активирај со"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Остварете го дејството со %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Заврши го дејството"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Отвори со"</string> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index 375cb98b0c4f..b769fd23ab1e 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"അൺലോക്ക് ചെയ്യുന്നതിനായി മെനു അമർത്തുക അല്ലെങ്കിൽ അടിയന്തര കോൾ വിളിക്കുക."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"അൺലോക്കുചെയ്യാൻ മെനു അമർത്തുക."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"അൺലോക്ക് ചെയ്യാൻ പാറ്റേൺ വരയ്ക്കുക"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"എമർജൻസി"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"കോളിലേക്ക് മടങ്ങുക"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ശരി!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"വീണ്ടും ശ്രമിക്കുക"</string> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 1472c4e9f178..7b41501e6a0f 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Яаралтай дуудлага хийх буюу эсвэл түгжээг тайлах бол цэсийг дарна уу."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Тайлах бол цэсийг дарна уу."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Тайлах хээгээ зурна уу"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Яаралтай тусламж"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Дуудлагаруу буцах"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Зөв!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Дахин оролдох"</string> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index 6695de6c7a33..577803334c33 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"अनलॉक करण्यासाठी मेनू दाबा किंवा आणीबाणीचा कॉल करा."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"अनलॉक करण्यासाठी मेनू दाबा."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"अनलॉक करण्यासाठी पॅटर्न काढा"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"आणीबाणी"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"कॉलवर परत या"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"अचूक!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"पुन्हा प्रयत्न करा"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index c8f37c305ff7..c30bcfc1800f 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Tekan Menu untuk menyahsekat atau membuat panggilan kecemasan."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Tekan Menu untuk membuka kunci."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Lukiskan corak untuk membuka kunci"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Kecemasan"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Kembali ke panggilan"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Betul!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Cuba lagi"</string> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index dcb4e3cfc634..243357eca3c2 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ဖွင့်ရန်သို့မဟုတ်အရေးပေါ်ခေါ်ဆိုခြင်းပြုလုပ်ရန် မီနူးကိုနှိပ်ပါ"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"မီးနူးကို နှိပ်ခြင်းဖြင့် သော့ဖွင့်ပါ"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ဖွင့်ရန်ပုံစံဆွဲပါ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"အရေးပေါ်"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ခေါ်ဆိုမှုထံပြန်သွားရန်"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"မှန်ပါသည်"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ထပ် စမ်းပါ"</string> @@ -1102,7 +1103,7 @@ <string name="delete" msgid="1514113991712129054">"ဖျက်ရန်"</string> <string name="copyUrl" msgid="6229645005987260230">"URLအား ကူးခြင်း"</string> <string name="selectTextMode" msgid="3225108910999318778">"စာသား ရွေးရန်"</string> - <string name="undo" msgid="3175318090002654673">"ပြန်ဖျက်ရန်"</string> + <string name="undo" msgid="3175318090002654673">"တစ်ဆင့်နောက်ပြန်ရန်"</string> <string name="redo" msgid="7231448494008532233">"ထပ်လုပ်ပါ"</string> <string name="autofill" msgid="511224882647795296">"အော်တိုဖြည့်"</string> <string name="textSelectionCABTitle" msgid="5151441579532476940">"စာတိုရွေးချယ်မှု"</string> @@ -1125,7 +1126,7 @@ <string name="capital_off" msgid="7443704171014626777">"ပိတ်"</string> <string name="checked" msgid="9179896827054513119">"အမှန်ခြစ်ပြီး"</string> <string name="not_checked" msgid="7972320087569023342">"ခြစ် မထား"</string> - <string name="whichApplication" msgid="5432266899591255759">"အသုံးပြု၍ ဆောင်ရွက်မှုအားပြီးဆုံးစေခြင်း"</string> + <string name="whichApplication" msgid="5432266899591255759">"အောက်ပါတို့ကို အသုံးပြုမှု အပြီးသတ်ခြင်း"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$s ကို သုံးပြီး လုပ်ဆောင်ချက် ပြီးဆုံးပါစေ"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"လုပ်ဆောင်ချက်ကို အပြီးသတ်ပါ"</string> <string name="whichViewApplication" msgid="5733194231473132945">"...ဖြင့် ဖွင့်မည်"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 6926d659ed3f..0a31b49c555c 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Trykk på menyknappen for å låse opp eller ringe et nødnummer."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Trykk på menyknappen for å låse opp."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Tegn mønster for å låse opp"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Nødssituasjon"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Tilbake til samtale"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Riktig!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Prøv på nytt"</string> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index c9ac6ec47001..9712b480b97b 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"अनलक वा आपतकालीन कल गर्न मेनु थिच्नुहोस्।"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"अनलक गर्न मेनु थिच्नुहोस्।"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"अनलक गर्नु ढाँचा खिच्नुहोस्"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"आपतकालीन"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"कलमा फर्किनुहोस्"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"सही!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"फेरि प्रयास गर्नुहोस्"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 904c90665229..d1a342c99590 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Druk op \'Menu\' om te ontgrendelen of noodoproep te plaatsen."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Druk op \'Menu\' om te ontgrendelen."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Patroon tekenen om te ontgrendelen"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Noodgeval"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Terug naar gesprek"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Juist!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Opnieuw proberen"</string> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 62505a31a169..84815f7a6133 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ଅନଲକ୍ କରିବା ପାଇଁ ମେନୁକୁ ଦବାନ୍ତୁ କିମ୍ବା ଜରୁରୀକାଳୀନ କଲ୍ କରନ୍ତୁ।"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ଅନଲକ୍ କରିବା ପାଇଁ ମେନୁକୁ ଦବାନ୍ତୁ।"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ଅନଲକ୍ କରିବା ପାଇଁ ପାଟର୍ନ ଆଙ୍କନ୍ତୁ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ଜରୁରୀକାଳୀନ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"କଲ୍କୁ ଫେରନ୍ତୁ"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ଠିକ୍!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ"</string> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 346ee9a476c7..c29308fd4c5b 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ ਜਾਂ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਕਰੋ।"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਮੀਨੂ ਦਬਾਓ।"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"ਅਣਲਾਕ ਕਰਨ ਲਈ ਪੈਟਰਨ ਡ੍ਰਾ ਕਰੋ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ਸੰਕਟਕਾਲ"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ਕਾਲ ਤੇ ਵਾਪਸ ਜਾਓ"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ਸਹੀ!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 7d2795e2db0f..df56457da65a 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Naciśnij Menu, aby odblokować lub wykonać połączenie alarmowe."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Naciśnij Menu, aby odblokować."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Narysuj wzór, aby odblokować"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Alarmowe"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Powrót do połączenia"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Poprawnie!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Spróbuj ponownie."</string> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 4fec5b3779a2..3ecf28d34ffd 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pressione Menu para desbloquear ou fazer uma chamada de emergência."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pressione Menu para desbloquear."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenhe o padrão para desbloquear"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergência"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Retornar à chamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correto!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Tente novamente"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 5f6fb5cf553c..dcc8e17609ac 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -286,7 +286,7 @@ <string name="notification_channel_retail_mode" msgid="3732239154256431213">"Demonstração para retalho"</string> <string name="notification_channel_usb" msgid="1528280969406244896">"Ligação USB"</string> <string name="notification_channel_heavy_weight_app" msgid="17455756500828043">"Aplicação em execução"</string> - <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Aplicações que estão a consumir bateria"</string> + <string name="notification_channel_foreground_service" msgid="7102189948158885178">"Apps que estão a consumir bateria"</string> <string name="foreground_service_app_in_background" msgid="1439289699671273555">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> está a consumir bateria."</string> <string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicações estão a consumir bateria."</string> <string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string> @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Prima Menu para desbloquear ou efectuar uma chamada de emergência."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Prima Menu para desbloquear."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenhar padrão para desbloquear"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergência"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Regressar à chamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correcto!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Tentar novamente"</string> @@ -1153,7 +1154,7 @@ <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Capturar imagem"</string> <string name="alwaysUse" msgid="3153558199076112903">"Utilizar por predefinição para esta ação."</string> <string name="use_a_different_app" msgid="4987790276170972776">"Utilizar outra app"</string> - <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Limpar a predefinição nas Definições do Sistema > Aplicações > Transferidas."</string> + <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Limpar a predefinição nas Definições do Sistema > Apps > Transferidas."</string> <string name="chooseActivity" msgid="8563390197659779956">"Escolha uma ação"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"Escolher uma app para o dispositivo USB"</string> <string name="noApplications" msgid="1186909265235544019">"Nenhuma app pode efetuar esta ação."</string> @@ -1181,7 +1182,7 @@ <string name="launch_warning_original" msgid="3332206576800169626">"<xliff:g id="APP_NAME">%1$s</xliff:g> foi originalmente iniciado."</string> <string name="screen_compat_mode_scale" msgid="8627359598437527726">"Escala"</string> <string name="screen_compat_mode_show" msgid="5080361367584709857">"Mostrar sempre"</string> - <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reative este modo nas Definições do Sistema > Aplicações > Transferidas."</string> + <string name="screen_compat_mode_hint" msgid="4032272159093750908">"Reative este modo nas Definições do Sistema > Apps > Transferidas."</string> <string name="unsupported_display_size_message" msgid="7265211375269394699">"<xliff:g id="APP_NAME">%1$s</xliff:g> não suporta a definição de Tamanho do ecrã atual e pode ter um comportamento inesperado."</string> <string name="unsupported_display_size_show" msgid="980129850974919375">"Mostrar sempre"</string> <string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> foi concebida para uma versão incompatível do SO Android e pode ter um comportamento inesperado. Pode estar disponível uma versão atualizada da app."</string> @@ -1274,7 +1275,7 @@ <string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Enviar"</string> <string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Cancelar"</string> <string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Memorizar a minha escolha"</string> - <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Pode alterar mais tarde em Definições > Aplicações"</string> + <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Pode alterar mais tarde em Definições > Apps"</string> <string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Permitir Sempre"</string> <string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Nunca Permitir"</string> <string name="sim_removed_title" msgid="5387212933992546283">"Cartão SIM removido"</string> @@ -1284,8 +1285,8 @@ <string name="sim_added_message" msgid="6602906609509958680">"Reinicie o aparelho para aceder à rede de telemóvel."</string> <string name="sim_restart_button" msgid="8481803851341190038">"Reiniciar"</string> <string name="install_carrier_app_notification_title" msgid="5712723402213090102">"Ativar o serviço móvel"</string> - <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Transfira a app do operador para ativar o seu novo SIM."</string> - <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Transfira a app <xliff:g id="APP_NAME">%1$s</xliff:g> para ativar o novo SIM."</string> + <string name="install_carrier_app_notification_text" msgid="2781317581274192728">"Descarregue a app do operador para ativar o seu novo SIM."</string> + <string name="install_carrier_app_notification_text_app_name" msgid="4086877327264106484">"Descarregue a app <xliff:g id="APP_NAME">%1$s</xliff:g> para ativar o novo SIM."</string> <string name="install_carrier_app_notification_button" msgid="6257740533102594290">"Transferir app"</string> <string name="carrier_app_notification_title" msgid="5815477368072060250">"Novo SIM inserido"</string> <string name="carrier_app_notification_text" msgid="6567057546341958637">"Toque para configurar"</string> @@ -2035,7 +2036,7 @@ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Esta app não recebeu autorização de gravação, mas pode capturar áudio através deste dispositivo USB."</string> <string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Página inicial"</string> <string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Anterior"</string> - <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Aplicações recentes"</string> + <string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Apps recentes"</string> <string name="accessibility_system_action_notifications_label" msgid="6083767351772162010">"Notificações"</string> <string name="accessibility_system_action_quick_settings_label" msgid="4583900123506773783">"Definições rápidas"</string> <string name="accessibility_system_action_power_dialog_label" msgid="8095341821683910781">"Caixa de diálogo de energia"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 4fec5b3779a2..3ecf28d34ffd 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pressione Menu para desbloquear ou fazer uma chamada de emergência."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pressione Menu para desbloquear."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenhe o padrão para desbloquear"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergência"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Retornar à chamada"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Correto!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Tente novamente"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index a0e35985b142..64936689ee6e 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Apăsați Meniu pentru a debloca sau pentru a efectua apeluri de urgență."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Apăsați Meniu pentru deblocare."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Desenați modelul pentru a debloca"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgență"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Reveniți la apel"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Corect!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Încercați din nou"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 1a65847fecf4..07abf6dbb28d 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Нажмите \"Меню\", чтобы разблокировать экран или вызвать службу экстренной помощи."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Для разблокировки нажмите \"Меню\"."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Введите графический ключ"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Экстренный вызов"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Вернуться к вызову"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Правильно!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Повторите попытку"</string> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index db759f62b8ae..d494a36520bf 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"අගුළු හැරීමට මෙනුව ඔබන්න හෝ හදිසි ඇමතුම ලබාගන්න."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"අගුළු හැරීමට මෙනු ඔබන්න."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"අගුළු ඇරීමට රටාව අඳින්න"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"හදිසි"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"ඇමතුම වෙත නැවත යන්න"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"නිවැරදියි!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"නැවත උත්සාහ කරන්න"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 5f80364368eb..52c53015743b 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Ak chcete odomknúť telefón alebo uskutočniť tiesňové volanie, stlačte Menu."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Telefón odomknete stlačením tlačidla Menu."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Odomknite nakreslením vzoru"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Stav tiesne"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Zavolať späť"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Správne!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Skúsiť znova"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index 4e3705976e5f..a43c900fb585 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Če želite odkleniti napravo ali opraviti klic v sili, pritisnite meni."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Če želite odkleniti, pritisnite meni."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Če želite odkleniti, narišite vzorec"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Klic v sili"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Nazaj na klic"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Pravilno."</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Poskusi znova"</string> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 30e75558bb76..625962fb9bd1 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Shtyp \"Meny\" për të shkyçur ose për të kryer telefonatë urgjence."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Shtyp \"Meny\" për të shkyçur."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Vizato modelin për ta shkyçur"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Urgjenca"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Kthehu te telefonata"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Saktë!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Provo sërish"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 6890b800ac1e..6101dac65f95 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -835,7 +835,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Притисните „Мени“ да бисте откључали телефон или упутите хитан позив."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Притисните „Мени“ за откључавање."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Унесите шаблон за откључавање"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Хитне службе"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Назад на позив"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Тачно!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Пробајте поново"</string> @@ -1145,7 +1146,7 @@ <string name="capital_off" msgid="7443704171014626777">"НЕ"</string> <string name="checked" msgid="9179896827054513119">"означено је"</string> <string name="not_checked" msgid="7972320087569023342">"није означено"</string> - <string name="whichApplication" msgid="5432266899591255759">"Довршавање радње помоћу"</string> + <string name="whichApplication" msgid="5432266899591255759">"Доврши радњу преко"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Завршите радњу помоћу апликације %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Заврши радњу"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Отворите помоћу"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 87b087203a6a..e30bb14384f9 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Tryck på Menu för att låsa upp eller ringa nödsamtal."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Tryck på Menu för att låsa upp."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Rita grafiskt lösenord för att låsa upp"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Nödsamtal"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Tillbaka till samtal"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Korrekt!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Försök igen"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index fbf4ccae2e4a..4e4c0f656c9f 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Bonyeza Menyu ili kufungua au kupiga simu ya dharura."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Bonyeza Menyu ili kufungua."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Chora ruwaza ili kufungua"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Dharura"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Rudi kwa kupiga simu"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Sahihi!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Jaribu tena"</string> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 731a796a9d08..a617248071e0 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"தடைநீக்க மெனுவை அழுத்தவும் அல்லது அவசர அழைப்பை மேற்கொள்ளவும்."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"திறக்க, மெனுவை அழுத்தவும்."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"திறக்க வடிவத்தை வரையவும்"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"அவசர அழைப்பு"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"அழைப்பிற்குத் திரும்பு"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"சரி!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"மீண்டும் முயற்சிக்கவும்"</string> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index a2ec7872113b..794fec85bcf7 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"అన్లాక్ చేయడానికి లేదా అత్యవసర కాల్ చేయడానికి మెను నొక్కండి."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"అన్లాక్ చేయడానికి మెను నొక్కండి."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"అన్లాక్ చేయడానికి నమూనాను గీయండి"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"అత్యవసరం"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"కాల్కు తిరిగి వెళ్లు"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"సరైనది!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"మళ్లీ ప్రయత్నించండి"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 6773014ec2f8..3deb9b2b7b82 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"กด เมนู เพื่อปลดล็อกหรือโทรฉุกเฉิน"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"กด เมนู เพื่อปลดล็อก"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"วาดรูปแบบเพื่อปลดล็อก"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"เหตุฉุกเฉิน"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"กลับสู่การโทร"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"ถูกต้อง!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"ลองอีกครั้ง"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index da3e4373382a..ccda1c46059e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Pindutin ang Menu upang i-unlock o magsagawa ng pang-emergency na tawag."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Pindutin ang Menu upang i-unlock."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Iguhit ang pattern upang i-unlock"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Emergency"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Bumalik sa tawag"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Tama!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Subukang muli"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index bf17f6c360d4..85c69860eb81 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Kilidi açmak veya acil çağrı yapmak için Menü\'ye basın."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Kilidi açmak için Menü\'ye basın."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Kilit açmak için deseni çizin"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Acil durum çağrısı"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Çağrıya dön"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Doğru!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Tekrar deneyin"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 9464efa043f0..8ae20a50f7f3 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -838,7 +838,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Натис. меню, щоб розбл. чи зробити авар. виклик."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Натисн. меню, щоб розбл."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Намал. ключ, щоб розбл."</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Екстрений виклик"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Поверн. до дзвін."</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Правильно!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Повторіть спробу"</string> @@ -1165,7 +1166,7 @@ <string name="capital_off" msgid="7443704171014626777">"ВИМК"</string> <string name="checked" msgid="9179896827054513119">"вибрано"</string> <string name="not_checked" msgid="7972320087569023342">"не вибрано"</string> - <string name="whichApplication" msgid="5432266899591255759">"Завершити дію за доп."</string> + <string name="whichApplication" msgid="5432266899591255759">"Що використовувати?"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Завершити дію за допомогою %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Завершити дію"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Відкрити за допомогою"</string> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index 15a1fe7d45f0..94c6cd450a6c 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"غیر مقفل کرنے کیلئے مینو دبائیں یا ہنگامی کال کریں۔"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"غیر مقفل کرنے کیلئے مینو دبائیں۔"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"غیر مقفل کرنے کیلئے پیٹرن کو ڈرا کریں"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"ہنگامی"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"کال پر واپس جائیں"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"صحیح!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"دوبارہ کوشش کریں"</string> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index 76d2c609b63d..74676dfcf226 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Qulfdan chiqarish yoki favqulodda qo‘ng‘iroqni amalga oshirish uchun \"Menyu\"ni bosing."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Qulfni ochish uchun \"Menyu\"ga bosing."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Qulfni ochish uchun grafik kalitni chizing"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Favqulodda chaqiruv"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Qo‘ng‘iroqni qaytarish"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"To‘g‘ri!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Qaytadan urining"</string> @@ -1125,7 +1126,7 @@ <string name="capital_off" msgid="7443704171014626777">"O"</string> <string name="checked" msgid="9179896827054513119">"belgilandi"</string> <string name="not_checked" msgid="7972320087569023342">"belgilanmadi"</string> - <string name="whichApplication" msgid="5432266899591255759">"Ilovani tanlang"</string> + <string name="whichApplication" msgid="5432266899591255759">"Nima ishlatilsin?"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"“%1$s” bilan ochish"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Amalni bajarish"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Ochish…"</string> @@ -1548,7 +1549,7 @@ <string name="launchBrowserDefault" msgid="6328349989932924119">"Brauzer ishga tushirilsinmi?"</string> <string name="SetupCallDefault" msgid="5581740063237175247">"Qo‘ng‘iroqni qabul qilasizmi?"</string> <string name="activity_resolver_use_always" msgid="5575222334666843269">"Har doim"</string> - <string name="activity_resolver_use_once" msgid="948462794469672658">"Faqat hozir"</string> + <string name="activity_resolver_use_once" msgid="948462794469672658">"Faqat shu safar"</string> <string name="activity_resolver_work_profiles_support" msgid="4071345609235361269">"“%1$s” ishchi profilni qo‘llab-quvvatlamaydi"</string> <string name="default_audio_route_name" product="tablet" msgid="367936735632195517">"Planshet"</string> <string name="default_audio_route_name" product="tv" msgid="4908971385068087367">"TV"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index 9f44841f573c..48f38583220d 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Nhấn vào Menu để mở khóa hoặc thực hiện cuộc gọi khẩn cấp."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Nhấn vào Menu để mở khóa."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Vẽ hình để mở khóa"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Khẩn cấp"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Quay lại cuộc gọi"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Chính xác!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Thử lại"</string> @@ -1125,8 +1126,8 @@ <string name="capital_off" msgid="7443704171014626777">"TẮT"</string> <string name="checked" msgid="9179896827054513119">"đã chọn"</string> <string name="not_checked" msgid="7972320087569023342">"chưa chọn"</string> - <string name="whichApplication" msgid="5432266899591255759">"Hoàn tất tác vụ đang sử dụng"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"Hoàn tất tác vụ bằng %1$s"</string> + <string name="whichApplication" msgid="5432266899591255759">"Hoàn tất thao tác bằng"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"Hoàn tất thao tác bằng %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Hoàn thành tác vụ"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Mở bằng"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"Mở bằng %1$s"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 32ac0cf297c4..bf381ec53751 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"按 Menu 解锁或进行紧急呼救。"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"按 MENU 解锁。"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"绘制解锁图案"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"紧急呼救"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"返回通话"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"正确!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"重试"</string> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 82b079fd98af..a3b20d128726 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"按選單鍵解鎖或撥打緊急電話。"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"按選單鍵解鎖。"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖形以解除鎖定螢幕"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"緊急電話"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"返回通話"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"正確!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"再試一次"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 02c02008dea3..f6543c9285d2 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"按下 [Menu] 解鎖或撥打緊急電話。"</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"按下 Menu 鍵解鎖。"</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"畫出解鎖圖案"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"緊急撥號"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"返回通話"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"正確!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"再試一次"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7fe333d4bc33..85f54e9f9f5e 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -832,7 +832,8 @@ <string name="lockscreen_instructions_when_pattern_enabled" msgid="7982445492532123308">"Chofoza Menyu ukuvula noma ukwenza ikholi ephuthumayo."</string> <string name="lockscreen_instructions_when_pattern_disabled" msgid="7434061749374801753">"Chofoza Menyu ukuvula."</string> <string name="lockscreen_pattern_instructions" msgid="3169991838169244941">"Dweba iphathini ukuvula"</string> - <string name="lockscreen_emergency_call" msgid="7500692654885445299">"Isimo esiphuthumayo"</string> + <!-- no translation found for lockscreen_emergency_call (7549683825868928636) --> + <skip /> <string name="lockscreen_return_to_call" msgid="3156883574692006382">"Buyela ekholini"</string> <string name="lockscreen_pattern_correct" msgid="8050630103651508582">"Lungile!"</string> <string name="lockscreen_pattern_wrong" msgid="2940138714468358458">"Zama futhi"</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index eb30c9be4eba..ac08d96ab303 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2173,6 +2173,29 @@ <attr name="required" /> </declare-styleable> + <!-- The <code>uses-native-library</code> specifies a native shared library that this + package requires to be linked against. Specifying this flag tells the + system to make the native library to be available to your app. + + <p>On devices running R or lower, this is ignored and the app has access to all + the public native shared libraries that are exported from the platform. This is + also ignored if the app is targeting R or lower. + + <p>This appears as a child tag of the + {@link #AndroidManifestApplication application} tag. --> + <declare-styleable name="AndroidManifestUsesNativeLibrary" parent="AndroidManifestApplication"> + <!-- Required name of the library you use. --> + <attr name="name" /> + <!-- Specify whether this native library is required for the application. + The default is true, meaning the application requires the + library, and does not want to be installed on devices that + don't support it. If you set this to false, then this will + allow the application to be installed even if the library + doesn't exist, and you will need to check for its presence + dynamically at runtime. --> + <attr name="required" /> + </declare-styleable> + <!-- The <code>uses-static-library</code> specifies a shared <strong>static</strong> library that this package requires to be statically linked against. Specifying this tag tells the system to include this library's code in your class loader. diff --git a/core/tests/coretests/src/android/graphics/PathTest.java b/core/tests/coretests/src/android/graphics/PathTest.java index c6d6d1ff90d5..b50792ca6b38 100644 --- a/core/tests/coretests/src/android/graphics/PathTest.java +++ b/core/tests/coretests/src/android/graphics/PathTest.java @@ -28,7 +28,9 @@ public class PathTest extends TestCase { final Path.FillType defaultFillType = path.getFillType(); final Path.FillType fillType = Path.FillType.INVERSE_EVEN_ODD; - assertFalse(fillType.equals(defaultFillType)); // Sanity check for the test itself. + + // This test is only meaningful if it changes from the default. + assertFalse(fillType.equals(defaultFillType)); path.setFillType(fillType); path.reset(); diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index a5a2221e5532..ada8b000a26b 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -39,6 +39,7 @@ <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.OBSERVE_NETWORK_POLICY"/> + <permission name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" /> <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/> <permission name="android.permission.PACKAGE_USAGE_STATS" /> <permission name="android.permission.READ_DREAM_STATE"/> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index e1f6b2aa76ab..dd8f40d586bc 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -263,10 +263,4 @@ be able to connect to the internet when such a proxy is in use, since all outgoing connections originate from this app. --> <allow-in-power-save-except-idle package="com.android.proxyhandler" /> - - <!-- These are the packages that are white-listed to be able to run as system user --> - <system-user-whitelisted-app package="com.android.settings" /> - - <!-- These are the packages that shouldn't run as system user --> - <system-user-blacklisted-app package="com.android.wallpaper.livepicker" /> </permissions> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 6798c0a3f87e..0286a7148b0b 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -143,6 +143,9 @@ applications that come with the platform <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" /> <permission name="android.permission.PACKAGE_USAGE_STATS" /> <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> + + <!-- For permission hub 2 debugging only --> + <permission name="android.permission.GET_ACCOUNTS_PRIVILEGED"/> </privapp-permissions> <privapp-permissions package="com.android.phone"> diff --git a/data/keyboards/Vendor_2e95_Product_7725.kl b/data/keyboards/Vendor_2e95_Product_7725.kl new file mode 100644 index 000000000000..7672e22f8adc --- /dev/null +++ b/data/keyboards/Vendor_2e95_Product_7725.kl @@ -0,0 +1,64 @@ +# 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. + +# +# Scuf Vantage Controller +# + +# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html + +# Square +key 0x130 BUTTON_X +# Cross +key 0x131 BUTTON_A +# Circle +key 0x132 BUTTON_B +# Triangle +key 0x133 BUTTON_Y + +key 0x134 BUTTON_L1 +key 0x135 BUTTON_R1 +key 0x136 BUTTON_L2 +key 0x137 BUTTON_R2 + +# L2 Trigger axis +axis 0x03 LTRIGGER +# R2 Trigger axis +axis 0x04 RTRIGGER + +# Left Analog Stick +axis 0x00 X +axis 0x01 Y +# Right Analog Stick +axis 0x02 Z +axis 0x05 RZ + +# Left stick click +key 0x13a BUTTON_THUMBL +# Right stick click +key 0x13b BUTTON_THUMBR + +# Hat +axis 0x10 HAT_X +axis 0x11 HAT_Y + +# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt +# Share +key 0x138 BUTTON_SELECT +# Options +key 0x139 BUTTON_START +# PS key +key 0x13c BUTTON_MODE +# Touchpad press +key 0x13d BUTTON_1 diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java index d8d96413a93d..43373ffbd3f4 100644 --- a/graphics/java/android/graphics/Region.java +++ b/graphics/java/android/graphics/Region.java @@ -409,10 +409,10 @@ public class Region implements Parcelable { mNativeRegion = ni; } - /* add dummy parameter so constructor can be called from jni without + /* Add an unused parameter so constructor can be called from jni without triggering 'not cloneable' exception */ @UnsupportedAppUsage - private Region(long ni, int dummy) { + private Region(long ni, int unused) { this(ni); } diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java index 6eba9acbab4e..3263f79888d6 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayChangeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import android.os.Handler; import android.os.RemoteException; diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java index 083c2439aa87..418973204add 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import android.annotation.Nullable; import android.content.Context; @@ -28,21 +28,16 @@ import android.view.Display; import android.view.IDisplayWindowListener; import android.view.IWindowManager; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.wm.DisplayChangeController.OnDisplayChangingListener; +import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener; import java.util.ArrayList; -import javax.inject.Inject; -import javax.inject.Singleton; - /** * This module deals with display rotations coming from WM. When WM starts a rotation: after it has * frozen the screen, it will call into this class. This will then call all registered local * controllers and give them a chance to queue up task changes to be applied synchronously with that * rotation. */ -@Singleton public class DisplayController { private static final String TAG = "DisplayController"; @@ -55,7 +50,7 @@ public class DisplayController { private final ArrayList<OnDisplaysChangedListener> mDisplayChangedListeners = new ArrayList<>(); /** - * Get's a display by id from DisplayManager. + * Gets a display by id from DisplayManager. */ public Display getDisplay(int displayId) { final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); @@ -169,10 +164,9 @@ public class DisplayController { } }; - @Inject - public DisplayController(Context context, @Main Handler mainHandler, + public DisplayController(Context context, Handler handler, IWindowManager wmService) { - mHandler = mainHandler; + mHandler = handler; mContext = context; mWmService = wmService; mChangeController = new DisplayChangeController(mHandler, mWmService); diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 89f469a438a9..338ece5afbc2 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -30,6 +30,7 @@ import android.os.ServiceManager; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -40,18 +41,12 @@ import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; import com.android.internal.view.IInputMethodManager; -import com.android.systemui.TransactionPool; -import com.android.systemui.dagger.qualifiers.Main; import java.util.ArrayList; -import javax.inject.Inject; -import javax.inject.Singleton; - /** * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode. */ -@Singleton public class DisplayImeController implements DisplayController.OnDisplaysChangedListener { private static final String TAG = "DisplayImeController"; @@ -66,21 +61,20 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private static final int DIRECTION_HIDE = 2; private static final int FLOATING_IME_BOTTOM_INSET = -80; - SystemWindows mSystemWindows; - final Handler mHandler; - final TransactionPool mTransactionPool; - - final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); - - final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); + protected final IWindowManager mWmService; + protected final Handler mHandler; + private final TransactionPool mTransactionPool; + private final DisplayController mDisplayController; + private final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>(); + private final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>(); - @Inject - public DisplayImeController(SystemWindows syswin, DisplayController displayController, - @Main Handler mainHandler, TransactionPool transactionPool) { + public DisplayImeController(IWindowManager wmService, DisplayController displayController, + Handler mainHandler, TransactionPool transactionPool) { mHandler = mainHandler; - mSystemWindows = syswin; + mWmService = wmService; mTransactionPool = transactionPool; - displayController.addDisplayWindowListener(this); + mDisplayController = displayController; + mDisplayController.addDisplayWindowListener(this); } @Override @@ -88,9 +82,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // Add's a system-ui window-manager specifically for ime. This type is special because // WM will defer IME inset handling to it in multi-window scenarious. PerDisplay pd = new PerDisplay(displayId, - mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()); + mDisplayController.getDisplayLayout(displayId).rotation()); try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } @@ -103,7 +97,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (pd == null) { return; } - if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation() + if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { pd.startAnimation(true, false /* forceRestart */); } @@ -112,7 +106,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged @Override public void onDisplayRemoved(int displayId) { try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } @@ -270,7 +264,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged private void setVisibleDirectly(boolean visible) { mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible); try { - mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { } } @@ -289,7 +283,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // an IME inset). For now, we assume that no non-floating IME will be <= this nav bar // frame height so any reported frame that is <= nav-bar frame height is assumed to // be floating. - return frame.height() <= mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId) + return frame.height() <= mDisplayController.getDisplayLayout(mDisplayId) .navBarFrameHeight(); } @@ -304,9 +298,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged // This is a "floating" or "expanded" IME, so to get animations, just // pretend the ime has some size just below the screen. mImeFrame.set(newFrame); - final int floatingInset = (int) ( - mSystemWindows.mDisplayController.getDisplayLayout(mDisplayId).density() - * FLOATING_IME_BOTTOM_INSET); + final int floatingInset = (int) (mDisplayController.getDisplayLayout(mDisplayId) + .density() * FLOATING_IME_BOTTOM_INSET); mImeFrame.bottom -= floatingInset; } else if (newFrame.height() != 0) { // Don't set a new frame if it's empty and hiding -- this maintains continuity @@ -364,6 +357,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mAnimation.setInterpolator(INTERPOLATOR); mAnimation.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; + @Override public void onAnimationStart(Animator animation) { SurfaceControl.Transaction t = mTransactionPool.acquire(); @@ -386,10 +380,12 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged t.apply(); mTransactionPool.release(t); } + @Override public void onAnimationCancel(Animator animation) { mCancelled = true; } + @Override public void onAnimationEnd(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationEnd " + mCancelled); @@ -449,18 +445,19 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged int IME_ANIMATION_NO_ALPHA = 1; /** @hide */ - @IntDef(prefix = { "IME_ANIMATION_" }, value = { + @IntDef(prefix = {"IME_ANIMATION_"}, value = { IME_ANIMATION_NO_ALPHA, }) - @interface ImeAnimationFlags {} + @interface ImeAnimationFlags { + } /** * Called when the IME position is starting to animate. * - * @param hiddenTop The y position of the top of the IME surface when it is hidden. - * @param shownTop The y position of the top of the IME surface when it is shown. - * @param showing {@code true} when we are animating from hidden to shown, {@code false} - * when animating from shown to hidden. + * @param hiddenTop The y position of the top of the IME surface when it is hidden. + * @param shownTop The y position of the top of the IME surface when it is shown. + * @param showing {@code true} when we are animating from hidden to shown, {@code false} + * when animating from shown to hidden. * @param isFloating {@code true} when the ime is a floating ime (doesn't inset). * @return flags that may alter how ime itself is animated (eg. no-alpha). */ @@ -476,8 +473,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged * * @param imeTop The current y position of the top of the IME surface. */ - default void onImePositionChanged(int displayId, int imeTop, - SurfaceControl.Transaction t) {} + default void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { + } /** * Called when the IME position is done animating. @@ -485,7 +482,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged * @param cancel {@code true} if this was cancelled. This implies another start is coming. */ default void onImeEndPositioning(int displayId, boolean cancel, - SurfaceControl.Transaction t) {} + SurfaceControl.Transaction t) { + } } public IInputMethodManager getImms() { diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java index a341f3050ea6..3181dbf74ace 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -52,7 +52,7 @@ 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 + * like insets/cutout/rotation. In general, this can be thought of as the shell analog to * DisplayPolicy. */ public class DisplayLayout { @@ -345,9 +345,9 @@ public class DisplayLayout { /** Retrieve the statusbar height from resources. */ static int getStatusBarHeight(boolean landscape, Resources res) { return landscape ? res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height_landscape) - : res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height_portrait); + com.android.internal.R.dimen.status_bar_height_landscape) + : res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height_portrait); } /** Calculate the DisplayCutout for a particular display size/rotation. */ diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 21f67aef5604..8abe9eeb6a9a 100644 --- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; @@ -52,23 +52,18 @@ import com.android.internal.os.IResultReceiver; import java.util.HashMap; -import javax.inject.Inject; -import javax.inject.Singleton; - /** - * Represents the "windowing" layer of the System-UI. This layer allows system-ui components to - * place and manipulate windows without talking to WindowManager. + * Represents the "windowing" layer of the WM Shell. This layer allows shell components to place and + * manipulate windows without talking to WindowManager. */ -@Singleton public class SystemWindows { private static final String TAG = "SystemWindows"; private final SparseArray<PerDisplay> mPerDisplay = new SparseArray<>(); - final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); - Context mContext; - IWindowSession mSession; - DisplayController mDisplayController; - IWindowManager mWmService; + private final HashMap<View, SurfaceControlViewHost> mViewRoots = new HashMap<>(); + private final DisplayController mDisplayController; + private final IWindowManager mWmService; + private IWindowSession mSession; private final DisplayController.OnDisplaysChangedListener mDisplayListener = new DisplayController.OnDisplaysChangedListener() { @@ -88,10 +83,7 @@ public class SystemWindows { public void onDisplayRemoved(int displayId) { } }; - @Inject - public SystemWindows(Context context, DisplayController displayController, - IWindowManager wmService) { - mContext = context; + public SystemWindows(DisplayController displayController, IWindowManager wmService) { mWmService = wmService; mDisplayController = displayController; mDisplayController.addDisplayWindowListener(mDisplayListener); @@ -172,7 +164,7 @@ public class SystemWindows { /** * Get the IWindow token for a specific root. * - * @param windowType A window type from {@link android.view.WindowManager}. + * @param windowType A window type from {@link WindowManager}. */ IWindow getWindow(int displayId, int windowType) { PerDisplay pd = mPerDisplay.get(displayId); @@ -215,8 +207,8 @@ public class SystemWindows { } final Display display = mDisplayController.getDisplay(mDisplayId); SurfaceControlViewHost viewRoot = - new SurfaceControlViewHost(mContext, display, wwm, - true /* useSfChoreographer */); + new SurfaceControlViewHost( + view.getContext(), display, wwm, true /* useSfChoreographer */); attrs.flags |= FLAG_HARDWARE_ACCELERATED; viewRoot.setView(view, attrs); mViewRoots.put(view, viewRoot); @@ -318,7 +310,7 @@ public class SystemWindows { } } - class ContainerWindow extends IWindow.Stub { + static class ContainerWindow extends IWindow.Stub { ContainerWindow() {} @Override diff --git a/packages/SystemUI/src/com/android/systemui/TransactionPool.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java index 801cf8a7523b..4c34566b0d98 100644 --- a/packages/SystemUI/src/com/android/systemui/TransactionPool.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java @@ -14,24 +14,19 @@ * limitations under the License. */ -package com.android.systemui; +package com.android.wm.shell.common; import android.util.Pools; import android.view.SurfaceControl; -import javax.inject.Inject; -import javax.inject.Singleton; - /** * Provides a synchronized pool of {@link SurfaceControl.Transaction}s to minimize allocations. */ -@Singleton public class TransactionPool { private final Pools.SynchronizedPool<SurfaceControl.Transaction> mTransactionPool = new Pools.SynchronizedPool<>(4); - @Inject - TransactionPool() { + public TransactionPool() { } /** Gets a transaction from the pool. */ diff --git a/libs/WindowManager/Shell/tests/Android.bp b/libs/WindowManager/Shell/tests/Android.bp index 78fa45ebdf94..9868879cebb9 100644 --- a/libs/WindowManager/Shell/tests/Android.bp +++ b/libs/WindowManager/Shell/tests/Android.bp @@ -36,9 +36,6 @@ android_test { "libstaticjvmtiagent", ], - sdk_version: "current", - platform_apis: true, - optimize: { enabled: false, }, diff --git a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.java index 376875b143a1..f1ead3c8a441 100644 --- a/libs/WindowManager/Shell/tests/src/com/android/wm/shell/tests/WindowManagerShellTest.java +++ b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/WindowManagerShellTest.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,13 +14,11 @@ * limitations under the License. */ -package com.android.wm.shell.tests; +package com.android.wm.shell; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import com.android.wm.shell.WindowManagerShell; - import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java index 9596a73eaf3e..2b5b77e49e3a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wm/DisplayLayoutTest.java +++ b/libs/WindowManager/Shell/tests/src/com/android/wm/shell/common/DisplayLayoutTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.wm; +package com.android.wm.shell.common; import static android.content.res.Configuration.UI_MODE_TYPE_NORMAL; import static android.view.Surface.ROTATION_0; @@ -35,12 +35,11 @@ import android.view.DisplayInfo; import androidx.test.filters.SmallTest; import com.android.internal.R; -import com.android.systemui.SysuiTestCase; import org.junit.Test; @SmallTest -public class DisplayLayoutTest extends SysuiTestCase { +public class DisplayLayoutTest { @Test public void testInsets() { diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp index d25fc4b0b03e..b2c39c90071a 100644 --- a/libs/hwui/JankTracker.cpp +++ b/libs/hwui/JankTracker.cpp @@ -139,6 +139,9 @@ void JankTracker::finishFrame(const FrameInfo& frame) { (*mGlobalData)->reportJank(); } + if (mSwapDeadline < 0) { + mSwapDeadline = frame[FrameInfoIndex::IntendedVsync] + mFrameInterval; + } bool isTripleBuffered = (mSwapDeadline - frame[FrameInfoIndex::IntendedVsync]) > (mFrameInterval * 0.1); mSwapDeadline = std::max(mSwapDeadline + mFrameInterval, diff --git a/libs/hwui/JankTracker.h b/libs/hwui/JankTracker.h index 4460266276f9..b3fbbfe98669 100644 --- a/libs/hwui/JankTracker.h +++ b/libs/hwui/JankTracker.h @@ -75,7 +75,7 @@ private: std::array<int64_t, NUM_BUCKETS> mThresholds; int64_t mFrameInterval; - nsecs_t mSwapDeadline; + nsecs_t mSwapDeadline = -1; // The amount of time we will erase from the total duration to account // for SF vsync offsets with HWC2 blocking dequeueBuffers. // (Vsync + mDequeueBlockTolerance) is the point at which we expect diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index c0a24438987a..1a89cfd5d0ad 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -209,11 +209,8 @@ static SkImageInfo validateAlpha(const SkImageInfo& info) { void Bitmap::reconfigure(const SkImageInfo& newInfo, size_t rowBytes) { mInfo = validateAlpha(newInfo); - // Dirty hack is dirty - // TODO: Figure something out here, Skia's current design makes this - // really hard to work with. Skia really, really wants immutable objects, - // but with the nested-ref-count hackery going on that's just not - // feasible without going insane trying to figure it out + // TODO: Skia intends for SkPixelRef to be immutable, but this method + // modifies it. Find another way to support reusing the same pixel memory. this->android_only_reset(mInfo.width(), mInfo.height(), rowBytes); } diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java index 496885cd1f37..997339eb2a80 100644 --- a/location/java/android/location/GpsStatus.java +++ b/location/java/android/location/GpsStatus.java @@ -151,6 +151,16 @@ public final class GpsStatus { return status; } + /** + * Builds an empty GpsStatus. Should only be used for legacy reasons. + * + * @hide + */ + @NonNull + static GpsStatus createEmpty() { + return new GpsStatus(); + } + private GpsStatus() { } diff --git a/location/java/android/location/ILocationListener.aidl b/location/java/android/location/ILocationListener.aidl index 6e7f6a52d669..29b483af8721 100644 --- a/location/java/android/location/ILocationListener.aidl +++ b/location/java/android/location/ILocationListener.aidl @@ -24,6 +24,6 @@ import android.os.IRemoteCallback; */ oneway interface ILocationListener { - void onLocationChanged(in Location location, in IRemoteCallback onCompleteCallback); + void onLocationChanged(in Location location, in @nullable IRemoteCallback onCompleteCallback); void onProviderEnabledChanged(String provider, boolean enabled); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index bb8f81dfaa32..0e7eaa21888e 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -113,7 +113,7 @@ interface ILocationManager List<LocationRequest> getTestProviderCurrentRequests(String provider); LocationTime getGnssTimeMillis(); - boolean sendExtraCommand(String provider, String command, inout Bundle extras); + void sendExtraCommand(String provider, String command, inout Bundle extras); // used by gts tests to verify whitelists String[] getBackgroundThrottlingWhitelist(); diff --git a/location/java/android/location/LocationListener.java b/location/java/android/location/LocationListener.java index 8df08345c79b..2738ff4ff38c 100644 --- a/location/java/android/location/LocationListener.java +++ b/location/java/android/location/LocationListener.java @@ -36,7 +36,9 @@ import android.os.Bundle; public interface LocationListener { /** - * Called when the location has changed. + * Called when the location has changed. A wakelock is held on behalf on the listener for some + * brief amount of time as this callback executes. If this callback performs long running + * operations, it is the client's responsibility to obtain their own wakelock. * * @param location the updated location */ @@ -52,18 +54,17 @@ public interface LocationListener { default void onStatusChanged(String provider, int status, Bundle extras) {} /** - * Called when the provider is enabled by the user. + * Called when a provider this listener is registered with becomes enabled. * - * @param provider the name of the location provider that has become enabled + * @param provider the name of the location provider */ default void onProviderEnabled(@NonNull String provider) {} /** - * Called when the provider is disabled by the user. If requestLocationUpdates - * is called on an already disabled provider, this method is called - * immediately. + * Called when the provider this listener is registered with becomes disabled. If a provider is + * disabled when this listener is registered, this callback will be invoked immediately. * - * @param provider the name of the location provider that has become disabled + * @param provider the name of the location provider */ default void onProviderDisabled(@NonNull String provider) {} } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index c0b8e1bf3bbe..1803027743f6 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -303,7 +303,6 @@ public class LocationManager { public static final String METADATA_SETTINGS_FOOTER_STRING = "com.android.settings.location.FOOTER_STRING"; - private static final long MAX_SINGLE_LOCATION_TIMEOUT_MS = 30 * 1000; @GuardedBy("sLocationListeners") @@ -311,7 +310,9 @@ public class LocationManager { sLocationListeners = new WeakHashMap<>(); final Context mContext; - @UnsupportedAppUsage + + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, publicAlternatives = "{@link " + + "LocationManager}") final ILocationManager mService; private final Object mLock = new Object(); @@ -421,8 +422,7 @@ public class LocationManager { try { return mService.getExtraLocationControllerPackage(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return null; + throw e.rethrowFromSystemServer(); } } @@ -437,7 +437,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackage(packageName); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -452,7 +452,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackageEnabled(enabled); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -466,8 +466,7 @@ public class LocationManager { try { return mService.isExtraLocationControllerPackageEnabled(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); - return false; + throw e.rethrowFromSystemServer(); } } @@ -485,7 +484,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackage(packageName); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -503,7 +502,7 @@ public class LocationManager { try { mService.setExtraLocationControllerPackageEnabled(enabled); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } @@ -1199,7 +1198,7 @@ public class LocationManager { mContext.getPackageName(), mContext.getAttributionTag(), AppOpsManager.toReceiverId(listener)); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -1301,7 +1300,7 @@ public class LocationManager { // unregistration is complete. mService.unregisterLocationListener(transport); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -1517,7 +1516,8 @@ public class LocationManager { Preconditions.checkArgument(command != null, "invalid null command"); try { - return mService.sendExtraCommand(provider, command, extras); + mService.sendExtraCommand(provider, command, extras); + return true; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1835,6 +1835,10 @@ public class LocationManager { } else { status.setStatus(gnssStatus, ttff); } + } else if (status == null) { + // even though this method is marked as nullable, legacy behavior was to never return + // a null result, and there are applications that rely on this behavior. + status = GpsStatus.createEmpty(); } return status; } @@ -2424,7 +2428,7 @@ public class LocationManager { try { cancellationSignal.cancel(); } catch (RemoteException e) { - e.rethrowFromSystemServer(); + throw e.rethrowFromSystemServer(); } } } @@ -2464,7 +2468,8 @@ public class LocationManager { } @Override - public void onLocationChanged(Location location, IRemoteCallback onCompleteCallback) { + public void onLocationChanged(Location location, + @Nullable IRemoteCallback onCompleteCallback) { executeSafely(mExecutor, () -> mListener, new ListenerOperation<LocationListener>() { @Override public void operate(LocationListener listener) { @@ -2473,7 +2478,13 @@ public class LocationManager { @Override public void onComplete(boolean success) { - markComplete(onCompleteCallback); + if (onCompleteCallback != null) { + try { + onCompleteCallback.sendResult(null); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } }); } @@ -2488,14 +2499,6 @@ public class LocationManager { } }); } - - private void markComplete(IRemoteCallback onCompleteCallback) { - try { - onCompleteCallback.sendResult(null); - } catch (RemoteException e) { - e.rethrowFromSystemServer(); - } - } } private static class NmeaAdapter extends GnssStatus.Callback implements OnNmeaMessageListener { diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index 00a4c7e19f34..6e3fb1991acc 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -18,7 +18,6 @@ package android.media; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.SystemApi; import android.util.SparseIntArray; import java.lang.annotation.Retention; @@ -137,13 +136,15 @@ public final class AudioDeviceInfo { */ public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; /** - * @hide * A device type for rerouting audio within the Android framework between mixes and - * system applications. Typically created when using - * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the - * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag. - */ - @SystemApi + * system applications. + * This type is for instance encountered when querying the output device of a track + * (with {@link AudioTrack#getRoutedDevice()} playing from a device in screen mirroring mode, + * where the audio is not heard on the device, but on the remote device. + */ + // Typically created when using + // {@link android.media.audiopolicy.AudioPolicy} for mixes created with the + // {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_LOOP_BACK} flag. public static final int TYPE_REMOTE_SUBMIX = 25; /** @hide */ diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 6d690f0aa397..4a6724a09c1e 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -1411,9 +1411,9 @@ public class ExifInterface { private static final int IMAGE_TYPE_WEBP = 14; static { - sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); + sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss", Locale.US); sFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); - sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX"); + sFormatterTz = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss XXX", Locale.US); sFormatterTz.setTimeZone(TimeZone.getTimeZone("UTC")); // Build up the hash tables to look up Exif tags for reading Exif tags. diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java index 3c2be5f93e30..029e61492b6d 100644 --- a/media/java/android/media/browse/MediaBrowser.java +++ b/media/java/android/media/browse/MediaBrowser.java @@ -25,7 +25,6 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ParceledListSlice; import android.media.MediaDescription; -import android.media.session.MediaController; import android.media.session.MediaSession; import android.os.Binder; import android.os.Bundle; @@ -757,8 +756,8 @@ public final class MediaBrowser { * Flag: Indicates that the item is playable. * <p> * The id of this item may be passed to - * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)} - * to start playing it. + * {@link android.media.session.MediaController.TransportControls + * #playFromMediaId(String, Bundle)} to start playing it. * </p> */ public static final int FLAG_PLAYABLE = 1 << 1; @@ -1107,13 +1106,7 @@ public final class MediaBrowser { } @Override - public void onLoadChildren(String parentId, ParceledListSlice list) { - onLoadChildrenWithOptions(parentId, list, null); - } - - @Override - public void onLoadChildrenWithOptions(String parentId, ParceledListSlice list, - final Bundle options) { + public void onLoadChildren(String parentId, ParceledListSlice list, Bundle options) { MediaBrowser mediaBrowser = mMediaBrowser.get(); if (mediaBrowser != null) { mediaBrowser.onLoadChildren(this, parentId, list, options); diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl index 7e3f2f8868fb..a8772076af97 100644 --- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl +++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl @@ -23,7 +23,5 @@ oneway interface IMediaBrowserServiceCallbacks { void onConnect(String root, in MediaSession.Token session, in Bundle extras); @UnsupportedAppUsage void onConnectFailed(); - void onLoadChildren(String mediaId, in ParceledListSlice list); - void onLoadChildrenWithOptions(String mediaId, in ParceledListSlice list, - in Bundle options); + void onLoadChildren(String mediaId, in ParceledListSlice list, in Bundle options); } diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java index 06adf30a8303..39c7682a2a74 100644 --- a/media/java/android/service/media/MediaBrowserService.java +++ b/media/java/android/service/media/MediaBrowserService.java @@ -687,7 +687,7 @@ public abstract class MediaBrowserService extends Service { final ParceledListSlice<MediaBrowser.MediaItem> pls = filteredList == null ? null : new ParceledListSlice<>(filteredList); try { - connection.callbacks.onLoadChildrenWithOptions(parentId, pls, options); + connection.callbacks.onLoadChildren(parentId, pls, options); } catch (RemoteException ex) { // The other side is in the process of crashing. Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 0dc76313cf5c..2b95992b1edf 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -24061,6 +24061,7 @@ package android.media { field public static final int TYPE_IP = 20; // 0x14 field public static final int TYPE_LINE_ANALOG = 5; // 0x5 field public static final int TYPE_LINE_DIGITAL = 6; // 0x6 + field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 field public static final int TYPE_TELEPHONY = 18; // 0x12 field public static final int TYPE_TV_TUNER = 17; // 0x11 field public static final int TYPE_UNKNOWN = 0; // 0x0 diff --git a/non-updatable-api/removed.txt b/non-updatable-api/removed.txt index ba05a1b89988..f2dfb84eb8fe 100644 --- a/non-updatable-api/removed.txt +++ b/non-updatable-api/removed.txt @@ -1,10 +1,6 @@ // Signature format: 2.0 package android.app { - public class ActivityManager { - method @Deprecated public static int getMaxNumPictureInPictureActions(); - } - public class Notification implements android.os.Parcelable { method @Deprecated public String getChannel(); method public static Class<? extends android.app.Notification.Style> getNotificationStyleClass(String); diff --git a/non-updatable-api/system-current.txt b/non-updatable-api/system-current.txt index b41ab93295b5..222e563d4f96 100644 --- a/non-updatable-api/system-current.txt +++ b/non-updatable-api/system-current.txt @@ -4112,10 +4112,6 @@ package android.media { field public static final int ROLE_OUTPUT = 2; // 0x2 } - public final class AudioDeviceInfo { - field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19 - } - public final class AudioFocusInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public android.media.AudioAttributes getAttributes(); diff --git a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml index 0a294246dfaa..09fbf7a59a8c 100644 --- a/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml +++ b/packages/CarSystemUI/res/layout/car_user_switching_dialog.xml @@ -15,7 +15,6 @@ ~ limitations under the License. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:fitsSystemWindows="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" diff --git a/packages/CarSystemUI/res/layout/super_notification_shade.xml b/packages/CarSystemUI/res/layout/super_notification_shade.xml deleted file mode 100644 index db71c91ca695..000000000000 --- a/packages/CarSystemUI/res/layout/super_notification_shade.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** 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. -*/ ---> - -<!-- This is the notification shade window. --> -<com.android.systemui.statusbar.phone.NotificationShadeWindowView - 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_height="match_parent" - android:fitsSystemWindows="true"> - - <com.android.systemui.statusbar.BackDropView - android:id="@+id/backdrop" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="gone" - sysui:ignoreRightInset="true" - > - <ImageView android:id="@+id/backdrop_back" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop"/> - <ImageView android:id="@+id/backdrop_front" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerCrop" - android:visibility="invisible"/> - </com.android.systemui.statusbar.BackDropView> - - <com.android.systemui.statusbar.ScrimView - android:id="@+id/scrim_behind" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:importantForAccessibility="no" - sysui:ignoreRightInset="true" - /> - - <include layout="@layout/brightness_mirror"/> - - <ViewStub android:id="@+id/fullscreen_user_switcher_stub" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout="@layout/car_fullscreen_user_switcher"/> - - <include layout="@layout/notification_center_activity" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_marginBottom="@dimen/navigation_bar_height" - android:visibility="invisible"/> - - <include layout="@layout/status_bar_expanded" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:visibility="invisible"/> - - <com.android.systemui.statusbar.ScrimView - android:id="@+id/scrim_in_front" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:importantForAccessibility="no" - sysui:ignoreRightInset="true" - /> - -</com.android.systemui.statusbar.phone.NotificationShadeWindowView> diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml deleted file mode 100644 index d93f62f8809d..000000000000 --- a/packages/CarSystemUI/res/layout/super_status_bar.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -** -** 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. -*/ ---> - -<!-- This is the status bar window. --> -<com.android.systemui.statusbar.phone.StatusBarWindowView - 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_height="match_parent" - android:fitsSystemWindows="true"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - > - <FrameLayout - android:id="@+id/status_bar_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" - /> - - <FrameLayout - android:id="@+id/car_top_navigation_bar_container" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - </LinearLayout> - -</com.android.systemui.statusbar.phone.StatusBarWindowView> diff --git a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml index 2dc499c160c6..2c9788955bfa 100644 --- a/packages/CarSystemUI/res/layout/sysui_overlay_window.xml +++ b/packages/CarSystemUI/res/layout/sysui_overlay_window.xml @@ -22,12 +22,10 @@ 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_marginBottom="@dimen/navigation_bar_height"/> + android:layout="@layout/notification_panel_container"/> <ViewStub android:id="@+id/keyguard_stub" android:layout_width="match_parent" diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml index cb321cdc6c4d..8359dac6a30f 100644 --- a/packages/CarSystemUI/res/values/dimens.xml +++ b/packages/CarSystemUI/res/values/dimens.xml @@ -81,6 +81,21 @@ <dimen name="car_keyline_2">96dp</dimen> <dimen name="car_keyline_3">128dp</dimen> + <!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon --> + <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen> + <!-- Margin between text lines in Ongoing App Ops dialog --> + <dimen name="ongoing_appops_dialog_text_margin">15dp</dimen> + <!-- Padding around Ongoing App Ops dialog content --> + <dimen name="ongoing_appops_dialog_content_padding">24dp</dimen> + <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 --> + <dimen name="ongoing_appops_chip_margin">12dp</dimen> + <!-- Start and End padding for Ongoing App Ops chip --> + <dimen name="ongoing_appops_chip_side_padding">6dp</dimen> + <!-- Padding between background of Ongoing App Ops chip and content --> + <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen> + <!-- Radius of Ongoing App Ops chip corners --> + <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen> + <!-- Car volume dimens. --> <dimen name="car_volume_item_icon_size">@dimen/car_primary_icon_size</dimen> <dimen name="car_volume_item_height">@*android:dimen/car_single_line_list_item_height</dimen> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java index ffdf378959c9..797a178c9a4b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java @@ -21,8 +21,6 @@ import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.car.navigationbar.CarNavigationBar; import com.android.systemui.car.notification.CarNotificationModule; import com.android.systemui.car.sideloaded.SideLoadedAppController; -import com.android.systemui.car.statusbar.CarStatusBar; -import com.android.systemui.car.statusbar.CarStatusBarModule; import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier; import com.android.systemui.car.volume.VolumeUI; import com.android.systemui.car.window.OverlayWindowModule; @@ -37,10 +35,10 @@ import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsModule; import com.android.systemui.shortcut.ShortcutKeyDispatcher; import com.android.systemui.stackdivider.Divider; +import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.notification.InstantAppNotifier; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.tv.TvStatusBar; import com.android.systemui.theme.ThemeOverlayController; import com.android.systemui.toast.ToastUI; import com.android.systemui.util.leak.GarbageMonitor; @@ -51,7 +49,7 @@ import dagger.multibindings.ClassKey; import dagger.multibindings.IntoMap; /** Binder for car specific {@link SystemUI} modules. */ -@Module(includes = {RecentsModule.class, CarStatusBarModule.class, NotificationsModule.class, +@Module(includes = {RecentsModule.class, StatusBarModule.class, NotificationsModule.class, BubbleModule.class, KeyguardModule.class, OverlayWindowModule.class, CarNotificationModule.class}) public abstract class CarSystemUIBinder { @@ -162,19 +160,7 @@ public abstract class CarSystemUIBinder { @Binds @IntoMap @ClassKey(StatusBar.class) - public abstract SystemUI bindsStatusBar(CarStatusBar sysui); - - /** Inject into TvStatusBar. */ - @Binds - @IntoMap - @ClassKey(TvStatusBar.class) - public abstract SystemUI bindsTvStatusBar(TvStatusBar sysui); - - /** Inject into StatusBarGoogle. */ - @Binds - @IntoMap - @ClassKey(CarStatusBar.class) - public abstract SystemUI bindsCarStatusBar(CarStatusBar sysui); + public abstract SystemUI bindsStatusBar(StatusBar sysui); /** Inject into VolumeUI. */ @Binds diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java index 496742680893..7b6dceb5fcd7 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java @@ -22,14 +22,13 @@ import static com.android.systemui.Dependency.LEAK_REPORT_EMAIL_NAME; import android.content.Context; import android.os.Handler; import android.os.PowerManager; +import android.view.IWindowManager; import com.android.keyguard.KeyguardViewController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.car.CarDeviceProvisionedControllerImpl; import com.android.systemui.car.keyguard.CarKeyguardViewController; -import com.android.systemui.car.statusbar.CarStatusBar; -import com.android.systemui.car.statusbar.CarStatusBarKeyguardViewManager; import com.android.systemui.car.statusbar.DozeServiceHost; import com.android.systemui.car.statusbar.DummyNotificationShadeWindowController; import com.android.systemui.car.volume.CarVolumeDialogComponent; @@ -59,16 +58,17 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.ShadeControllerImpl; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryControllerImpl; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.volume.VolumeDialogComponent; -import com.android.systemui.wm.DisplayImeController; import com.android.systemui.wm.DisplaySystemBarsController; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; import javax.inject.Named; import javax.inject.Singleton; @@ -99,10 +99,6 @@ public abstract class CarSystemUIModule { groupManager, configurationController); } - @Binds - abstract DisplayImeController bindDisplayImeController( - DisplaySystemBarsController displaySystemBarsController); - @Singleton @Provides @Named(LEAK_REPORT_EMAIL_NAME) @@ -117,6 +113,31 @@ public abstract class CarSystemUIModule { return new Recents(context, recentsImplementation, commandQueue); } + @Singleton + @Provides + static TransactionPool provideTransactionPool() { + return new TransactionPool(); + } + + @Singleton + @Provides + static DisplayController providerDisplayController(Context context, @Main Handler handler, + IWindowManager wmService) { + return new DisplayController(context, handler, wmService); + } + + @Singleton + @Provides + static SystemWindows provideSystemWindows(DisplayController displayController, + IWindowManager wmService) { + return new SystemWindows(displayController, wmService); + } + + @Singleton + @Binds + abstract DisplayImeController bindDisplayImeController( + DisplaySystemBarsController displaySystemBarsController); + @Binds abstract HeadsUpManager bindHeadsUpManagerPhone(HeadsUpManagerPhone headsUpManagerPhone); @@ -158,17 +179,10 @@ public abstract class CarSystemUIModule { CarSystemUIRootComponent systemUIRootComponent); @Binds - public abstract StatusBar bindStatusBar(CarStatusBar statusBar); - - @Binds abstract VolumeDialogComponent bindVolumeDialogComponent( CarVolumeDialogComponent carVolumeDialogComponent); @Binds - abstract StatusBarKeyguardViewManager bindStatusBarKeyguardViewManager( - CarStatusBarKeyguardViewManager keyguardViewManager); - - @Binds abstract KeyguardViewController bindKeyguardViewController( CarKeyguardViewController carKeyguardViewController); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 69766cc6c0d0..51a7245ea5c6 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -141,6 +141,11 @@ public class CarKeyguardViewController extends OverlayViewController implements } @Override + protected boolean shouldShowNavigationBar() { + return true; + } + + @Override public void onFinishInflate() { mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, 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 1eead62c042a..8d5843635e5f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/NotificationPanelViewController.java @@ -16,6 +16,8 @@ package com.android.systemui.car.notification; +import static android.view.WindowInsets.Type.navigationBars; + import android.app.ActivityManager; import android.car.Car; import android.car.drivingstate.CarUxRestrictionsManager; @@ -197,6 +199,16 @@ public class NotificationPanelViewController extends OverlayPanelViewController } @Override + protected boolean shouldShowStatusBar() { + return true; + } + + @Override + protected int getInsetTypesToFit() { + return navigationBars(); + } + + @Override protected boolean shouldShowHUN() { return mEnableHeadsUpNotificationWhenNotificationShadeOpen; } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java deleted file mode 100644 index d692487d410e..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBar.java +++ /dev/null @@ -1,519 +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.statusbar; - -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.PowerManager; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.View; - -import com.android.internal.logging.MetricsLogger; -import com.android.internal.statusbar.RegisterStatusBarResult; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.BatteryMeterView; -import com.android.systemui.Dependency; -import com.android.systemui.InitController; -import com.android.systemui.Prefs; -import com.android.systemui.R; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.bubbles.BubbleController; -import com.android.systemui.car.CarDeviceProvisionedController; -import com.android.systemui.car.CarDeviceProvisionedListener; -import com.android.systemui.car.bluetooth.CarBatteryController; -import com.android.systemui.car.navigationbar.CarNavigationBarController; -import com.android.systemui.classifier.FalsingLog; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.UiBackground; -import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.plugins.qs.QS; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.stackdivider.Divider; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.DozeScrimController; -import com.android.systemui.statusbar.phone.DozeServiceHost; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; -import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LightsOutNotifController; -import com.android.systemui.statusbar.phone.LockscreenLockIconController; -import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBar; -import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; -import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.volume.VolumeComponent; - -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Named; -import javax.inject.Provider; - -import dagger.Lazy; - -/** - * A status bar tailored for the automotive use case. - */ -public class CarStatusBar extends StatusBar implements CarBatteryController.BatteryViewHandler { - private static final String TAG = "CarStatusBar"; - - private final UserSwitcherController mUserSwitcherController; - private final ScrimController mScrimController; - - private CarBatteryController mCarBatteryController; - private BatteryMeterView mBatteryMeterView; - private Drawable mNotificationPanelBackground; - - private final Object mQueueLock = new Object(); - private final CarNavigationBarController mCarNavigationBarController; - private final CarDeviceProvisionedController mCarDeviceProvisionedController; - private final ScreenLifecycle mScreenLifecycle; - - private boolean mDeviceIsSetUpForUser = true; - private boolean mIsUserSetupInProgress = false; - - public CarStatusBar( - Context context, - NotificationsController notificationsController, - LightBarController lightBarController, - AutoHideController autoHideController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - StatusBarIconController statusBarIconController, - PulseExpansionHandler pulseExpansionHandler, - NotificationWakeUpCoordinator notificationWakeUpCoordinator, - KeyguardBypassController keyguardBypassController, - KeyguardStateController keyguardStateController, - HeadsUpManagerPhone headsUpManagerPhone, - DynamicPrivacyController dynamicPrivacyController, - BypassHeadsUpNotifier bypassHeadsUpNotifier, - FalsingManager falsingManager, - BroadcastDispatcher broadcastDispatcher, - RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, - NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptStateProvider, - NotificationViewHierarchyManager notificationViewHierarchyManager, - KeyguardViewMediator keyguardViewMediator, - DisplayMetrics displayMetrics, - MetricsLogger metricsLogger, - @UiBackground Executor uiBgExecutor, - NotificationMediaManager notificationMediaManager, - NotificationLockscreenUserManager lockScreenUserManager, - NotificationRemoteInputManager remoteInputManager, - UserSwitcherController userSwitcherController, - NetworkController networkController, - BatteryController batteryController, - SysuiColorExtractor colorExtractor, - ScreenLifecycle screenLifecycle, - WakefulnessLifecycle wakefulnessLifecycle, - SysuiStatusBarStateController statusBarStateController, - VibratorHelper vibratorHelper, - BubbleController bubbleController, - NotificationGroupManager groupManager, - VisualStabilityManager visualStabilityManager, - CarDeviceProvisionedController carDeviceProvisionedController, - NavigationBarController navigationBarController, - Lazy<AssistManager> assistManagerLazy, - ConfigurationController configurationController, - NotificationShadeWindowController notificationShadeWindowController, - LockscreenLockIconController lockscreenLockIconController, - DozeParameters dozeParameters, - ScrimController scrimController, - Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, - Lazy<BiometricUnlockController> biometricUnlockControllerLazy, - DozeServiceHost dozeServiceHost, - PowerManager powerManager, - ScreenPinningRequest screenPinningRequest, - DozeScrimController dozeScrimController, - VolumeComponent volumeComponent, - CommandQueue commandQueue, - Optional<Recents> recents, - Provider<StatusBarComponent.Builder> statusBarComponentBuilder, - PluginManager pluginManager, - Optional<Divider> dividerOptional, - SuperStatusBarViewFactory superStatusBarViewFactory, - LightsOutNotifController lightsOutNotifController, - StatusBarNotificationActivityStarter.Builder - statusBarNotificationActivityStarterBuilder, - ShadeController shadeController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - ViewMediatorCallback viewMediatorCallback, - InitController initController, - DarkIconDispatcher darkIconDispatcher, - @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, - PluginDependencyProvider pluginDependencyProvider, - KeyguardDismissUtil keyguardDismissUtil, - ExtensionController extensionController, - UserInfoControllerImpl userInfoControllerImpl, - PhoneStatusBarPolicy phoneStatusBarPolicy, - KeyguardIndicationController keyguardIndicationController, - DismissCallbackRegistry dismissCallbackRegistry, - StatusBarTouchableRegionManager statusBarTouchableRegionManager, - Lazy<NotificationShadeDepthController> depthControllerLazy, - /* Car Settings injected components. */ - CarNavigationBarController carNavigationBarController) { - super( - context, - notificationsController, - lightBarController, - autoHideController, - keyguardUpdateMonitor, - statusBarIconController, - pulseExpansionHandler, - notificationWakeUpCoordinator, - keyguardBypassController, - keyguardStateController, - headsUpManagerPhone, - dynamicPrivacyController, - bypassHeadsUpNotifier, - falsingManager, - broadcastDispatcher, - remoteInputQuickSettingsDisabler, - notificationGutsManager, - notificationLogger, - notificationInterruptStateProvider, - notificationViewHierarchyManager, - keyguardViewMediator, - displayMetrics, - metricsLogger, - uiBgExecutor, - notificationMediaManager, - lockScreenUserManager, - remoteInputManager, - userSwitcherController, - networkController, - batteryController, - colorExtractor, - screenLifecycle, - wakefulnessLifecycle, - statusBarStateController, - vibratorHelper, - bubbleController, - groupManager, - visualStabilityManager, - carDeviceProvisionedController, - navigationBarController, - assistManagerLazy, - configurationController, - notificationShadeWindowController, - lockscreenLockIconController, - dozeParameters, - scrimController, - null /* keyguardLiftController */, - lockscreenWallpaperLazy, - biometricUnlockControllerLazy, - dozeServiceHost, - powerManager, - screenPinningRequest, - dozeScrimController, - volumeComponent, - commandQueue, - recents, - statusBarComponentBuilder, - pluginManager, - dividerOptional, - lightsOutNotifController, - statusBarNotificationActivityStarterBuilder, - shadeController, - superStatusBarViewFactory, - statusBarKeyguardViewManager, - viewMediatorCallback, - initController, - darkIconDispatcher, - timeTickHandler, - pluginDependencyProvider, - keyguardDismissUtil, - extensionController, - userInfoControllerImpl, - phoneStatusBarPolicy, - keyguardIndicationController, - dismissCallbackRegistry, - depthControllerLazy, - statusBarTouchableRegionManager); - mUserSwitcherController = userSwitcherController; - mScrimController = scrimController; - mCarDeviceProvisionedController = carDeviceProvisionedController; - mCarNavigationBarController = carNavigationBarController; - mScreenLifecycle = screenLifecycle; - } - - @Override - public void start() { - mDeviceIsSetUpForUser = mCarDeviceProvisionedController.isCurrentUserSetup(); - mIsUserSetupInProgress = mCarDeviceProvisionedController.isCurrentUserSetupInProgress(); - - super.start(); - - createBatteryController(); - mCarBatteryController.startListening(); - - mCarDeviceProvisionedController.addCallback( - new CarDeviceProvisionedListener() { - @Override - public void onUserSetupInProgressChanged() { - mDeviceIsSetUpForUser = mCarDeviceProvisionedController - .isCurrentUserSetup(); - mIsUserSetupInProgress = mCarDeviceProvisionedController - .isCurrentUserSetupInProgress(); - } - - @Override - public void onUserSetupChanged() { - mDeviceIsSetUpForUser = mCarDeviceProvisionedController - .isCurrentUserSetup(); - mIsUserSetupInProgress = mCarDeviceProvisionedController - .isCurrentUserSetupInProgress(); - } - - @Override - public void onUserSwitched() { - mDeviceIsSetUpForUser = mCarDeviceProvisionedController - .isCurrentUserSetup(); - mIsUserSetupInProgress = mCarDeviceProvisionedController - .isCurrentUserSetupInProgress(); - } - }); - - mNotificationInterruptStateProvider.addSuppressor(new NotificationInterruptSuppressor() { - @Override - public String getName() { - return TAG; - } - - @Override - public boolean suppressInterruptions(NotificationEntry entry) { - // Because space is usually constrained in the auto use-case, there should not be a - // pinned notification when the shade has been expanded. - // Ensure this by not allowing any interruptions (ie: pinning any notifications) if - // the shade is already opened. - return !getPresenter().isPresenterFullyCollapsed(); - } - }); - } - - @Override - public boolean hideKeyguard() { - boolean result = super.hideKeyguard(); - mCarNavigationBarController.hideAllKeyguardButtons(isDeviceSetupForUser()); - return result; - } - - @Override - public void showKeyguard() { - super.showKeyguard(); - mCarNavigationBarController.showAllKeyguardButtons(isDeviceSetupForUser()); - } - - private boolean isDeviceSetupForUser() { - return mDeviceIsSetUpForUser && !mIsUserSetupInProgress; - } - - @Override - protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { - super.makeStatusBarView(result); - - mNotificationPanelBackground = getDefaultWallpaper(); - mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); - - FragmentHostManager manager = FragmentHostManager.get(mPhoneStatusBarWindow); - manager.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> { - mBatteryMeterView = fragment.getView().findViewById(R.id.battery); - - // By default, the BatteryMeterView should not be visible. It will be toggled - // when a device has connected by bluetooth. - mBatteryMeterView.setVisibility(View.GONE); - }); - } - - @Override - public void animateExpandNotificationsPanel() { - // No op. - } - - @Override - protected QS createDefaultQSFragment() { - return null; - } - - private BatteryController createBatteryController() { - mCarBatteryController = new CarBatteryController(mContext); - mCarBatteryController.addBatteryViewHandler(this); - return mCarBatteryController; - } - - @Override - protected void createNavigationBar(@Nullable RegisterStatusBarResult result) { - // No op. - } - - @Override - public void notifyBiometricAuthModeChanged() { - // No op. - } - - @Override - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - //When executing dump() function simultaneously, we need to serialize them - //to get mStackScroller's position correctly. - synchronized (mQueueLock) { - pw.println(" mStackScroller: " + viewInfo(mStackScroller)); - pw.println(" mStackScroller: " + viewInfo(mStackScroller) - + " scroll " + mStackScroller.getScrollX() - + "," + mStackScroller.getScrollY()); - } - pw.print(" mCarBatteryController="); - pw.println(mCarBatteryController); - pw.print(" mBatteryMeterView="); - pw.println(mBatteryMeterView); - - if (Dependency.get(KeyguardUpdateMonitor.class) != null) { - Dependency.get(KeyguardUpdateMonitor.class).dump(fd, pw, args); - } - - FalsingLog.dump(pw); - - pw.println("SharedPreferences:"); - for (Map.Entry<String, ?> entry : Prefs.getAll(mContext).entrySet()) { - pw.print(" "); - pw.print(entry.getKey()); - pw.print("="); - pw.println(entry.getValue()); - } - } - - @Override - public void showBatteryView() { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "showBatteryView(). mBatteryMeterView: " + mBatteryMeterView); - } - - if (mBatteryMeterView != null) { - mBatteryMeterView.setVisibility(View.VISIBLE); - } - } - - @Override - public void hideBatteryView() { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "hideBatteryView(). mBatteryMeterView: " + mBatteryMeterView); - } - - if (mBatteryMeterView != null) { - mBatteryMeterView.setVisibility(View.GONE); - } - } - - @Override - protected void createUserSwitcher() { - if (!mUserSwitcherController.useFullscreenUserSwitcher()) { - super.createUserSwitcher(); - } - } - - /** - * Dismisses the keyguard and shows bouncer if authentication is necessary. - */ - public void dismissKeyguard() { - // Don't dismiss keyguard when the screen is off. - if (mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_OFF) { - return; - } - executeRunnableDismissingKeyguard(null/* runnable */, null /* cancelAction */, - true /* dismissShade */, true /* afterKeyguardGone */, true /* deferred */); - } - - /** - * Ensures that relevant child views are appropriately recreated when the device's density - * changes. - */ - @Override - public void onDensityOrFontScaleChanged() { - super.onDensityOrFontScaleChanged(); - // Need to update the background on density changed in case the change was due to night - // mode. - mNotificationPanelBackground = getDefaultWallpaper(); - mScrimController.setScrimBehindDrawable(mNotificationPanelBackground); - } - - /** - * Returns the {@link Drawable} that represents the wallpaper that the user has currently set. - */ - private Drawable getDefaultWallpaper() { - return mContext.getDrawable(com.android.internal.R.drawable.default_wallpaper); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java deleted file mode 100644 index 96a998a500e1..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarKeyguardViewManager.java +++ /dev/null @@ -1,137 +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.statusbar; - -import android.content.Context; -import android.view.View; - -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.R; -import com.android.systemui.car.navigationbar.CarNavigationBarController; -import com.android.systemui.dock.DockManager; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.phone.NavigationModeController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; - -import java.util.HashSet; -import java.util.Set; - -import javax.inject.Inject; -import javax.inject.Singleton; - -/** Car implementation of the {@link StatusBarKeyguardViewManager}. */ -@Singleton -public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager { - - protected boolean mShouldHideNavBar; - private final CarNavigationBarController mCarNavigationBarController; - private Set<OnKeyguardCancelClickedListener> mKeygaurdCancelClickedListenerSet; - - @Inject - public CarStatusBarKeyguardViewManager(Context context, - ViewMediatorCallback callback, - LockPatternUtils lockPatternUtils, - SysuiStatusBarStateController sysuiStatusBarStateController, - ConfigurationController configurationController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - NavigationModeController navigationModeController, - DockManager dockManager, - NotificationShadeWindowController notificationShadeWindowController, - KeyguardStateController keyguardStateController, - NotificationMediaManager notificationMediaManager, - CarNavigationBarController carNavigationBarController) { - super(context, callback, lockPatternUtils, sysuiStatusBarStateController, - configurationController, keyguardUpdateMonitor, navigationModeController, - dockManager, notificationShadeWindowController, keyguardStateController, - notificationMediaManager); - mShouldHideNavBar = context.getResources() - .getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown); - mCarNavigationBarController = carNavigationBarController; - mKeygaurdCancelClickedListenerSet = new HashSet<>(); - } - - @Override - protected void updateNavigationBarVisibility(boolean navBarVisible) { - if (!mShouldHideNavBar) { - return; - } - int visibility = navBarVisible ? View.VISIBLE : View.GONE; - mCarNavigationBarController.setBottomWindowVisibility(visibility); - mCarNavigationBarController.setLeftWindowVisibility(visibility); - mCarNavigationBarController.setRightWindowVisibility(visibility); - } - - /** - * Car is a multi-user system. There's a cancel button on the bouncer that allows the user to - * go back to the user switcher and select another user. Different user may have different - * security mode which requires bouncer container to be resized. For this reason, the bouncer - * view is destroyed on cancel. - */ - @Override - protected boolean shouldDestroyViewOnReset() { - return true; - } - - /** - * Called when cancel button in bouncer is pressed. - */ - @Override - public void onCancelClicked() { - mKeygaurdCancelClickedListenerSet.forEach(OnKeyguardCancelClickedListener::onCancelClicked); - } - - /** - * Do nothing on this change. - * The base class hides the keyguard which for automotive we want to avoid b/c this would happen - * on a configuration change due to day/night (headlight state). - */ - @Override - public void onDensityOrFontScaleChanged() { } - - /** - * Add listener for keyguard cancel clicked. - */ - public void addOnKeyguardCancelClickedListener( - OnKeyguardCancelClickedListener keyguardCancelClickedListener) { - mKeygaurdCancelClickedListenerSet.add(keyguardCancelClickedListener); - } - - /** - * Remove listener for keyguard cancel clicked. - */ - public void removeOnKeyguardCancelClickedListener( - OnKeyguardCancelClickedListener keyguardCancelClickedListener) { - mKeygaurdCancelClickedListenerSet.remove(keyguardCancelClickedListener); - } - - - /** - * Defines a callback for keyguard cancel button clicked listeners. - */ - public interface OnKeyguardCancelClickedListener { - /** - * Called when keyguard cancel button is clicked. - */ - void onCancelClicked(); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java deleted file mode 100644 index dc2eb04c2990..000000000000 --- a/packages/CarSystemUI/src/com/android/systemui/car/statusbar/CarStatusBarModule.java +++ /dev/null @@ -1,283 +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.statusbar; - -import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; - -import android.content.Context; -import android.os.Handler; -import android.os.PowerManager; -import android.util.DisplayMetrics; - -import com.android.internal.logging.MetricsLogger; -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.ViewMediatorCallback; -import com.android.systemui.InitController; -import com.android.systemui.assist.AssistManager; -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.bubbles.BubbleController; -import com.android.systemui.car.CarDeviceProvisionedController; -import com.android.systemui.car.navigationbar.CarNavigationBarController; -import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.dagger.qualifiers.UiBackground; -import com.android.systemui.keyguard.DismissCallbackRegistry; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.plugins.DarkIconDispatcher; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.PluginDependencyProvider; -import com.android.systemui.recents.Recents; -import com.android.systemui.recents.ScreenPinningRequest; -import com.android.systemui.shared.plugins.PluginManager; -import com.android.systemui.stackdivider.Divider; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.KeyguardIndicationController; -import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.NotificationLockscreenUserManager; -import com.android.systemui.statusbar.NotificationMediaManager; -import com.android.systemui.statusbar.NotificationRemoteInputManager; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.NotificationViewHierarchyManager; -import com.android.systemui.statusbar.PulseExpansionHandler; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; -import com.android.systemui.statusbar.SysuiStatusBarStateController; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.dagger.StatusBarDependenciesModule; -import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator; -import com.android.systemui.statusbar.notification.VisualStabilityManager; -import com.android.systemui.statusbar.notification.init.NotificationsController; -import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier; -import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider; -import com.android.systemui.statusbar.notification.logging.NotificationLogger; -import com.android.systemui.statusbar.notification.row.NotificationGutsManager; -import com.android.systemui.statusbar.notification.row.NotificationRowModule; -import com.android.systemui.statusbar.phone.AutoHideController; -import com.android.systemui.statusbar.phone.BiometricUnlockController; -import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.phone.DozeScrimController; -import com.android.systemui.statusbar.phone.DozeServiceHost; -import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; -import com.android.systemui.statusbar.phone.KeyguardBypassController; -import com.android.systemui.statusbar.phone.KeyguardDismissUtil; -import com.android.systemui.statusbar.phone.LightBarController; -import com.android.systemui.statusbar.phone.LightsOutNotifController; -import com.android.systemui.statusbar.phone.LockscreenLockIconController; -import com.android.systemui.statusbar.phone.LockscreenWallpaper; -import com.android.systemui.statusbar.phone.NotificationGroupManager; -import com.android.systemui.statusbar.phone.NotificationShadeWindowController; -import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.ScrimController; -import com.android.systemui.statusbar.phone.ShadeController; -import com.android.systemui.statusbar.phone.StatusBarIconController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter; -import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; -import com.android.systemui.statusbar.phone.dagger.StatusBarPhoneDependenciesModule; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.ExtensionController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.statusbar.policy.NetworkController; -import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; -import com.android.systemui.statusbar.policy.UserInfoControllerImpl; -import com.android.systemui.statusbar.policy.UserSwitcherController; -import com.android.systemui.volume.VolumeComponent; - -import java.util.Optional; -import java.util.concurrent.Executor; - -import javax.inject.Named; -import javax.inject.Provider; -import javax.inject.Singleton; - -import dagger.Lazy; -import dagger.Module; -import dagger.Provides; - -/** - * Dagger Module providing {@link CarStatusBar}. - */ -@Module(includes = {StatusBarDependenciesModule.class, StatusBarPhoneDependenciesModule.class, - NotificationRowModule.class}) -public class CarStatusBarModule { - /** - * Provides our instance of StatusBar which is considered optional. - */ - @Provides - @Singleton - static CarStatusBar provideStatusBar( - Context context, - NotificationsController notificationsController, - LightBarController lightBarController, - AutoHideController autoHideController, - KeyguardUpdateMonitor keyguardUpdateMonitor, - StatusBarIconController statusBarIconController, - PulseExpansionHandler pulseExpansionHandler, - NotificationWakeUpCoordinator notificationWakeUpCoordinator, - KeyguardBypassController keyguardBypassController, - KeyguardStateController keyguardStateController, - HeadsUpManagerPhone headsUpManagerPhone, - DynamicPrivacyController dynamicPrivacyController, - BypassHeadsUpNotifier bypassHeadsUpNotifier, - FalsingManager falsingManager, - BroadcastDispatcher broadcastDispatcher, - RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, - NotificationGutsManager notificationGutsManager, - NotificationLogger notificationLogger, - NotificationInterruptStateProvider notificationInterruptionStateProvider, - NotificationViewHierarchyManager notificationViewHierarchyManager, - KeyguardViewMediator keyguardViewMediator, - DisplayMetrics displayMetrics, - MetricsLogger metricsLogger, - @UiBackground Executor uiBgExecutor, - NotificationMediaManager notificationMediaManager, - NotificationLockscreenUserManager lockScreenUserManager, - NotificationRemoteInputManager remoteInputManager, - UserSwitcherController userSwitcherController, - NetworkController networkController, - BatteryController batteryController, - SysuiColorExtractor colorExtractor, - ScreenLifecycle screenLifecycle, - WakefulnessLifecycle wakefulnessLifecycle, - SysuiStatusBarStateController statusBarStateController, - VibratorHelper vibratorHelper, - BubbleController bubbleController, - NotificationGroupManager groupManager, - VisualStabilityManager visualStabilityManager, - CarDeviceProvisionedController carDeviceProvisionedController, - NavigationBarController navigationBarController, - Lazy<AssistManager> assistManagerLazy, - ConfigurationController configurationController, - NotificationShadeWindowController notificationShadeWindowController, - LockscreenLockIconController lockscreenLockIconController, - DozeParameters dozeParameters, - ScrimController scrimController, - Lazy<LockscreenWallpaper> lockscreenWallpaperLazy, - Lazy<BiometricUnlockController> biometricUnlockControllerLazy, - DozeServiceHost dozeServiceHost, - PowerManager powerManager, - ScreenPinningRequest screenPinningRequest, - DozeScrimController dozeScrimController, - VolumeComponent volumeComponent, - CommandQueue commandQueue, - Optional<Recents> recentsOptional, - Provider<StatusBarComponent.Builder> statusBarComponentBuilder, - PluginManager pluginManager, - Optional<Divider> dividerOptional, - SuperStatusBarViewFactory superStatusBarViewFactory, - LightsOutNotifController lightsOutNotifController, - StatusBarNotificationActivityStarter.Builder - statusBarNotificationActivityStarterBuilder, - ShadeController shadeController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, - ViewMediatorCallback viewMediatorCallback, - InitController initController, - DarkIconDispatcher darkIconDispatcher, - @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler, - PluginDependencyProvider pluginDependencyProvider, - KeyguardDismissUtil keyguardDismissUtil, - ExtensionController extensionController, - UserInfoControllerImpl userInfoControllerImpl, - PhoneStatusBarPolicy phoneStatusBarPolicy, - KeyguardIndicationController keyguardIndicationController, - DismissCallbackRegistry dismissCallbackRegistry, - StatusBarTouchableRegionManager statusBarTouchableRegionManager, - Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy, - CarNavigationBarController carNavigationBarController) { - return new CarStatusBar( - context, - notificationsController, - lightBarController, - autoHideController, - keyguardUpdateMonitor, - statusBarIconController, - pulseExpansionHandler, - notificationWakeUpCoordinator, - keyguardBypassController, - keyguardStateController, - headsUpManagerPhone, - dynamicPrivacyController, - bypassHeadsUpNotifier, - falsingManager, - broadcastDispatcher, - remoteInputQuickSettingsDisabler, - notificationGutsManager, - notificationLogger, - notificationInterruptionStateProvider, - notificationViewHierarchyManager, - keyguardViewMediator, - displayMetrics, - metricsLogger, - uiBgExecutor, - notificationMediaManager, - lockScreenUserManager, - remoteInputManager, - userSwitcherController, - networkController, - batteryController, - colorExtractor, - screenLifecycle, - wakefulnessLifecycle, - statusBarStateController, - vibratorHelper, - bubbleController, - groupManager, - visualStabilityManager, - carDeviceProvisionedController, - navigationBarController, - assistManagerLazy, - configurationController, - notificationShadeWindowController, - lockscreenLockIconController, - dozeParameters, - scrimController, - lockscreenWallpaperLazy, - biometricUnlockControllerLazy, - dozeServiceHost, - powerManager, - screenPinningRequest, - dozeScrimController, - volumeComponent, - commandQueue, - recentsOptional, - statusBarComponentBuilder, - pluginManager, - dividerOptional, - superStatusBarViewFactory, - lightsOutNotifController, - statusBarNotificationActivityStarterBuilder, - shadeController, - statusBarKeyguardViewManager, - viewMediatorCallback, - initController, - darkIconDispatcher, - timeTickHandler, - pluginDependencyProvider, - keyguardDismissUtil, - extensionController, - userInfoControllerImpl, - phoneStatusBarPolicy, - keyguardIndicationController, - dismissCallbackRegistry, - statusBarTouchableRegionManager, - notificationShadeDepthControllerLazy, - carNavigationBarController); - } -} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java index 45f3d342fb6e..0d77c1341ffb 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserSwitchTransitionViewController.java @@ -91,6 +91,11 @@ public class UserSwitchTransitionViewController extends OverlayViewController { R.integer.config_userSwitchTransitionViewShownTimeoutMs); } + @Override + protected int getInsetTypesToFit() { + return 0; + } + /** * Makes the user switch transition view appear and draws the content inside of it if a user * that is different from the previous user is provided and if the dialog is not already diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java index 3969f92c690a..53deb9d9dc5d 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewController.java @@ -16,9 +16,12 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.statusBars; + import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.WindowInsets; /** * Owns a {@link View} that is present in SystemUIOverlayWindow. @@ -140,9 +143,25 @@ public class OverlayViewController { } /** + * Returns {@code true} if status bar should be displayed over this view. + */ + protected boolean shouldShowStatusBar() { + return false; + } + + /** * Returns {@code true} if this view should be hidden during the occluded state. */ protected boolean shouldShowWhenOccluded() { return false; } + + /** + * Returns the insets types to fit to the sysui overlay window when this + * {@link OverlayViewController} is in the foreground. + */ + @WindowInsets.Type.InsetsType + protected int getInsetTypesToFit() { + return statusBars(); + } } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java index 8e9410964313..2494242c24f0 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/OverlayViewGlobalStateController.java @@ -16,13 +16,17 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; +import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + import android.annotation.Nullable; import android.util.Log; +import android.view.WindowInsets.Type.InsetsType; +import android.view.WindowInsetsController; import androidx.annotation.VisibleForTesting; -import com.android.systemui.car.navigationbar.CarNavigationBarController; - import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -48,10 +52,7 @@ public class OverlayViewGlobalStateController { private static final String TAG = OverlayViewGlobalStateController.class.getSimpleName(); private static final int UNKNOWN_Z_ORDER = -1; private final SystemUIOverlayWindowController mSystemUIOverlayWindowController; - private final CarNavigationBarController mCarNavigationBarController; - - private boolean mIsOccluded; - + private final WindowInsetsController mWindowInsetsController; @VisibleForTesting Map<OverlayViewController, Integer> mZOrderMap; @VisibleForTesting @@ -60,14 +61,15 @@ public class OverlayViewGlobalStateController { Set<OverlayViewController> mViewsHiddenForOcclusion; @VisibleForTesting OverlayViewController mHighestZOrder; + private boolean mIsOccluded; @Inject public OverlayViewGlobalStateController( - CarNavigationBarController carNavigationBarController, SystemUIOverlayWindowController systemUIOverlayWindowController) { mSystemUIOverlayWindowController = systemUIOverlayWindowController; mSystemUIOverlayWindowController.attach(); - mCarNavigationBarController = carNavigationBarController; + mWindowInsetsController = + mSystemUIOverlayWindowController.getBaseLayout().getWindowInsetsController(); mZOrderMap = new HashMap<>(); mZOrderVisibleSortedMap = new TreeMap<>(); mViewsHiddenForOcclusion = new HashSet<>(); @@ -115,7 +117,9 @@ public class OverlayViewGlobalStateController { } updateInternalsWhenShowingView(viewController); + refreshInsetTypesToFit(); refreshNavigationBarVisibility(); + refreshStatusBarVisibility(); Log.d(TAG, "Content shown: " + viewController.getClass().getName()); debugLog(); @@ -185,7 +189,9 @@ public class OverlayViewGlobalStateController { mZOrderVisibleSortedMap.remove(mZOrderMap.get(viewController)); refreshHighestZOrderWhenHidingView(viewController); + refreshInsetTypesToFit(); refreshNavigationBarVisibility(); + refreshStatusBarVisibility(); if (mZOrderVisibleSortedMap.isEmpty()) { setWindowVisible(false); @@ -208,10 +214,28 @@ public class OverlayViewGlobalStateController { } private void refreshNavigationBarVisibility() { + mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowNavigationBar()) { - mCarNavigationBarController.showBars(); + mWindowInsetsController.show(navigationBars()); } else { - mCarNavigationBarController.hideBars(); + mWindowInsetsController.hide(navigationBars()); + } + } + + private void refreshStatusBarVisibility() { + mWindowInsetsController.setSystemBarsBehavior(BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); + if (mZOrderVisibleSortedMap.isEmpty() || mHighestZOrder.shouldShowStatusBar()) { + mWindowInsetsController.show(statusBars()); + } else { + mWindowInsetsController.hide(statusBars()); + } + } + + private void refreshInsetTypesToFit() { + if (mZOrderVisibleSortedMap.isEmpty()) { + setFitInsetsTypes(statusBars()); + } else { + setFitInsetsTypes(mHighestZOrder.getInsetTypesToFit()); } } @@ -224,6 +248,10 @@ public class OverlayViewGlobalStateController { mSystemUIOverlayWindowController.setWindowVisible(visible); } + private void setFitInsetsTypes(@InsetsType int types) { + mSystemUIOverlayWindowController.setFitInsetsTypes(types); + } + /** * Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the * sysui overlay window. diff --git a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java index bcd96f63a2b4..029bd3702afe 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/window/SystemUIOverlayWindowController.java @@ -25,6 +25,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import com.android.systemui.R; @@ -99,7 +100,6 @@ public class SystemUIOverlayWindowController implements PixelFormat.TRANSLUCENT); mLp.token = new Binder(); mLp.gravity = Gravity.TOP; - mLp.setFitInsetsTypes(/* types= */ 0); mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; mLp.setTitle("SystemUIOverlayWindow"); mLp.packageName = mContext.getPackageName(); @@ -110,6 +110,12 @@ public class SystemUIOverlayWindowController implements setWindowVisible(false); } + /** Sets the types of insets to fit. Note: This should be rarely used. */ + public void setFitInsetsTypes(@WindowInsets.Type.InsetsType int types) { + mLpChanged.setFitInsetsTypes(types); + updateWindow(); + } + /** Sets the window to the visible state. */ public void setWindowVisible(boolean visible) { mVisible = visible; diff --git a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java index a831464e7987..5c80202ba592 100644 --- a/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java +++ b/packages/CarSystemUI/src/com/android/systemui/wm/DisplaySystemBarsController.java @@ -16,12 +16,14 @@ package com.android.systemui.wm; +import android.content.Context; import android.os.Handler; import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.view.IDisplayWindowInsetsController; +import android.view.IWindowManager; import android.view.InsetsController; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -29,8 +31,10 @@ import android.view.WindowInsets; import androidx.annotation.VisibleForTesting; -import com.android.systemui.TransactionPool; import com.android.systemui.dagger.qualifiers.Main; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.TransactionPool; import javax.inject.Inject; import javax.inject.Singleton; @@ -47,29 +51,32 @@ public class DisplaySystemBarsController extends DisplayImeController { private static final String TAG = "DisplaySystemBarsController"; private SparseArray<PerDisplay> mPerDisplaySparseArray; + private final Context mContext; @Inject public DisplaySystemBarsController( - SystemWindows syswin, + Context context, + IWindowManager wmService, DisplayController displayController, @Main Handler mainHandler, TransactionPool transactionPool) { - super(syswin, displayController, mainHandler, transactionPool); + super(wmService, displayController, mainHandler, transactionPool); + mContext = context; } @Override public void onDisplayAdded(int displayId) { PerDisplay pd = new PerDisplay(displayId); try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd); + mWmService.setDisplayWindowInsetsController(displayId, pd); } catch (RemoteException e) { Slog.w(TAG, "Unable to set insets controller on display " + displayId); } // Lazy loading policy control filters instead of during boot. if (mPerDisplaySparseArray == null) { mPerDisplaySparseArray = new SparseArray<>(); - BarControlPolicy.reloadFromSetting(mSystemWindows.mContext); - BarControlPolicy.registerContentObserver(mSystemWindows.mContext, mHandler, () -> { + BarControlPolicy.reloadFromSetting(mContext); + BarControlPolicy.registerContentObserver(mContext, mHandler, () -> { int size = mPerDisplaySparseArray.size(); for (int i = 0; i < size; i++) { mPerDisplaySparseArray.valueAt(i).modifyDisplayWindowInsets(); @@ -82,7 +89,7 @@ public class DisplaySystemBarsController extends DisplayImeController { @Override public void onDisplayRemoved(int displayId) { try { - mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null); + mWmService.setDisplayWindowInsetsController(displayId, null); } catch (RemoteException e) { Slog.w(TAG, "Unable to remove insets controller on display " + displayId); } @@ -152,7 +159,7 @@ public class DisplaySystemBarsController extends DisplayImeController { showInsets(barVisibilities[0], /* fromIme= */ false); hideInsets(barVisibilities[1], /* fromIme= */ false); try { - mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); + mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState); } catch (RemoteException e) { Slog.w(TAG, "Unable to update window manager service."); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java index 20f9bc8ec1cb..ff286650ea50 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/window/OverlayViewGlobalStateControllerTest.java @@ -16,9 +16,14 @@ package com.android.systemui.car.window; +import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowInsets.Type.statusBars; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,19 +33,18 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.WindowInsetsController; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarSystemUiTest; -import com.android.systemui.car.navigationbar.CarNavigationBarController; 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.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; @@ -58,8 +62,6 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private ViewGroup mBaseLayout; @Mock - private CarNavigationBarController mCarNavigationBarController; - @Mock private SystemUIOverlayWindowController mSystemUIOverlayWindowController; @Mock private OverlayViewMediator mOverlayViewMediator; @@ -71,18 +73,22 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private OverlayPanelViewController mOverlayPanelViewController; @Mock private Runnable mRunnable; + @Mock + private WindowInsetsController mWindowInsetsController; @Before public void setUp() { MockitoAnnotations.initMocks(/* testClass= */ this); - mBaseLayout = (ViewGroup) LayoutInflater.from(mContext).inflate( - R.layout.overlay_view_global_state_controller_test, /* root= */ null); + mBaseLayout = spy((ViewGroup) LayoutInflater.from(mContext).inflate( + R.layout.overlay_view_global_state_controller_test, /* root= */ null)); + + when(mBaseLayout.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); mOverlayViewGlobalStateController = new OverlayViewGlobalStateController( - mCarNavigationBarController, mSystemUIOverlayWindowController); + mSystemUIOverlayWindowController); verify(mSystemUIOverlayWindowController).attach(); } @@ -108,7 +114,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -118,7 +124,37 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_nothingAlreadyShown_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_nothingAlreadyShown_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController1(); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_nothingAlreadyShown_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -168,10 +204,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setOverlayViewControllerAsShowing(mOverlayViewController1); setupOverlayViewController2(); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -183,7 +220,46 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_newHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_newHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController2, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -216,10 +292,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(true); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -231,7 +308,44 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void showView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarsHidden() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void showView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarsShown() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void showView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.showView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -402,10 +516,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setupOverlayViewController2(); setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController1.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -418,7 +533,48 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_newHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void hideView_newHighestZOrder_shouldShowStatusBarTrue_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_newHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(navigationBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(statusBars()); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController2, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -441,10 +597,11 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { setupOverlayViewController2(); setOverlayViewControllerAsShowing(mOverlayViewController2); when(mOverlayViewController2.shouldShowNavigationBar()).thenReturn(false); + reset(mWindowInsetsController); mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).hideBars(); + verify(mWindowInsetsController).hide(navigationBars()); } @Test @@ -457,7 +614,48 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_oldHighestZOrder_shouldShowStatusBarFalse_statusBarHidden() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(false); + reset(mWindowInsetsController); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).hide(statusBars()); + } + + @Test + public void hideView_oldHighestZOrder_shouldShowStatusBarTrue_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController2.shouldShowStatusBar()).thenReturn(true); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_oldHighestZOrder_fitsNavBarInsets_insetsAdjusted() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + setupOverlayViewController2(); + setOverlayViewControllerAsShowing(mOverlayViewController2); + when(mOverlayViewController1.getInsetTypesToFit()).thenReturn(statusBars()); + when(mOverlayViewController2.getInsetTypesToFit()).thenReturn(navigationBars()); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(navigationBars()); } @Test @@ -479,7 +677,27 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); - verify(mCarNavigationBarController).showBars(); + verify(mWindowInsetsController).show(navigationBars()); + } + + @Test + public void hideView_viewControllerOnlyShown_statusBarShown() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mWindowInsetsController).show(statusBars()); + } + + @Test + public void hideView_viewControllerOnlyShown_insetsAdjustedToDefault() { + setupOverlayViewController1(); + setOverlayViewControllerAsShowing(mOverlayViewController1); + + mOverlayViewGlobalStateController.hideView(mOverlayViewController1, mRunnable); + + verify(mSystemUIOverlayWindowController).setFitInsetsTypes(statusBars()); } @Test @@ -615,7 +833,7 @@ public class OverlayViewGlobalStateControllerTest extends SysuiTestCase { private void setOverlayViewControllerAsShowing(OverlayViewController overlayViewController) { mOverlayViewGlobalStateController.showView(overlayViewController, /* show= */ null); - Mockito.reset(mCarNavigationBarController, mSystemUIOverlayWindowController); + reset(mSystemUIOverlayWindowController); when(mSystemUIOverlayWindowController.getBaseLayout()).thenReturn(mBaseLayout); } } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java index 29cc8eec4bc3..391f75e35382 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/wm/DisplaySystemBarsControllerTest.java @@ -33,7 +33,8 @@ import android.view.IWindowManager; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.TransactionPool; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.TransactionPool; import org.junit.Before; import org.junit.Test; @@ -51,8 +52,6 @@ public class DisplaySystemBarsControllerTest extends SysuiTestCase { private static final int DISPLAY_ID = 1; @Mock - private SystemWindows mSystemWindows; - @Mock private IWindowManager mIWindowManager; @Mock private DisplayController mDisplayController; @@ -64,11 +63,10 @@ public class DisplaySystemBarsControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mSystemWindows.mContext = mContext; - mSystemWindows.mWmService = mIWindowManager; mController = new DisplaySystemBarsController( - mSystemWindows, + mContext, + mIWindowManager, mDisplayController, mHandler, mTransactionPool diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java index 16ef59f201f1..02f4457bffdb 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceChooserActivity.java @@ -29,6 +29,7 @@ import android.text.Html; import android.util.Log; import android.view.Gravity; import android.view.View; +import android.widget.AdapterView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; @@ -75,6 +76,14 @@ public class DeviceChooserActivity extends Activity { mDeviceListView = findViewById(R.id.device_list); final DeviceDiscoveryService.DevicesAdapter adapter = getService().mDevicesAdapter; mDeviceListView.setAdapter(adapter); + mDeviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> adapterView, View view, int pos, long l) { + getService().mSelectedDevice = + (DeviceFilterPair) adapterView.getItemAtPosition(pos); + adapter.notifyDataSetChanged(); + } + }); adapter.registerDataSetObserver(new DataSetObserver() { @Override public void onChanged() { @@ -157,4 +166,4 @@ public class DeviceChooserActivity extends Activity { new Intent().putExtra(CompanionDeviceManager.EXTRA_DEVICE, selectedDevice.device)); finish(); } -}
\ No newline at end of file +} diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index 7aa997e39307..bcaee367b03c 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -349,10 +349,6 @@ public class DeviceDiscoveryService extends Service { ? WIFI_ICON : BLUETOOTH_ICON, null, null, null); - textView.setOnClickListener((view) -> { - mSelectedDevice = device; - notifyDataSetChanged(); - }); } //TODO move to a layout file diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index d480ff63d8e3..508cbfccffe9 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -140,8 +140,8 @@ <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Rede aberta"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Rede segura"</string> <string name="process_kernel_label" msgid="950292573930336765">"SO Android"</string> - <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Aplicações removidas"</string> - <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Aplicações e utilizadores removidos"</string> + <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Apps removidas"</string> + <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Apps e utilizadores removidos"</string> <string name="data_usage_ota" msgid="7984667793701597001">"Atualizações do sistema"</string> <string name="tether_settings_title_usb" msgid="3728686573430917722">"Ligação USB"</string> <string name="tether_settings_title_wifi" msgid="4803402057533895526">"Hotspot portátil"</string> @@ -365,7 +365,7 @@ <string name="transition_animation_scale_title" msgid="1278477690695439337">"Escala de animação de transição"</string> <string name="animator_duration_scale_title" msgid="7082913931326085176">"Escala de duração de animação"</string> <string name="overlay_display_devices_title" msgid="5411894622334469607">"Simular apresentações secundárias"</string> - <string name="debug_applications_category" msgid="5394089406638954196">"Aplicações"</string> + <string name="debug_applications_category" msgid="5394089406638954196">"Apps"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"Não manter atividades"</string> <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"Destruir atividades assim que o utilizador sair"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"Limite do processo em 2º plano"</string> @@ -396,7 +396,7 @@ <item msgid="4548987861791236754">"Cores naturais e realistas"</item> <item msgid="1282170165150762976">"Cores otimizadas para conteúdos digitais"</item> </string-array> - <string name="inactive_apps_title" msgid="5372523625297212320">"Aplicações em espera"</string> + <string name="inactive_apps_title" msgid="5372523625297212320">"Apps em espera"</string> <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Inativo. Toque para ativar/desativar."</string> <string name="inactive_app_active_summary" msgid="8047630990208722344">"Ativo. Toque para ativar/desativar."</string> <string name="standby_bucket_summary" msgid="5128193447550429600">"Estado do Modo de espera das apps:<xliff:g id="BUCKET"> %s</xliff:g>"</string> diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java index 6269a717b333..fd986e5d13fd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java @@ -90,7 +90,7 @@ public class AccessPointPreference extends Preference { return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; } - // Used for dummy pref. + // Used for fake pref. public AccessPointPreference(Context context, AttributeSet attrs) { super(context, attrs); mFrictionSld = null; @@ -142,7 +142,7 @@ public class AccessPointPreference extends Preference { public void onBindViewHolder(final PreferenceViewHolder view) { super.onBindViewHolder(view); if (mAccessPoint == null) { - // Used for dummy pref. + // Used for fake pref. return; } Drawable drawable = getIcon(); diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java index 3015397ff1a3..bf5ab1c9951a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java @@ -256,7 +256,7 @@ public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestro } /** - * Sanity warning: this wipes out mScoreCache, so use with extreme caution + * Validity warning: this wipes out mScoreCache, so use with extreme caution * @param workThread substitute Handler thread, for testing purposes only */ @VisibleForTesting diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java index 0e6a60bf47c1..1ace0b4250b9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java @@ -118,7 +118,7 @@ public class WifiUtils { final int maxDisplayedScans = 4; int num5 = 0; // number of scanned BSSID on 5GHz band int num24 = 0; // number of scanned BSSID on 2.4Ghz band - int numBlackListed = 0; + int numBlockListed = 0; // TODO: sort list by RSSI or age long nowMs = SystemClock.elapsedRealtime(); @@ -170,8 +170,8 @@ public class WifiUtils { } visibility.append(scans5GHz.toString()); } - if (numBlackListed > 0) { - visibility.append("!").append(numBlackListed); + if (numBlockListed > 0) { + visibility.append("!").append(numBlockListed); } visibility.append("]"); diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 7f7afcbf11f5..8253c5e642f3 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -214,6 +214,9 @@ <!-- Permission needed to test tcp keepalive offload. --> <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> + <!-- Permission needed for CTS test - UnsupportedErrorDialogTests --> + <uses-permission android:name="android.permission.RESET_APP_ERRORS" /> + <!-- Permission needed to run keyguard manager tests in CTS --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index dfc47587b91c..2fbd9ba05817 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -45,7 +45,7 @@ android_library { "WindowManager-Shell", "SystemUIPluginLib", "SystemUISharedLib", - "SystemUI-statsd", + "SystemUI-statsd", "SettingsLib", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 6aa233b2f292..98d3553287d1 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -239,6 +239,7 @@ <!-- Listen app op changes --> <uses-permission android:name="android.permission.WATCH_APPOPS" /> + <uses-permission android:name="android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS" /> <!-- to read and change hvac values in a car --> <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" /> @@ -395,19 +396,15 @@ <!-- Springboard for launching the share and edit activity. This needs to be in the main system ui process since we need to notify the status bar to dismiss the keyguard --> - <receiver android:name=".screenshot.GlobalScreenshot$ActionProxyReceiver" - android:exported="false" /> - - <!-- Callback for dismissing screenshot notification after a share target is picked --> - <receiver android:name=".screenshot.GlobalScreenshot$TargetChosenReceiver" + <receiver android:name=".screenshot.ActionProxyReceiver" android:exported="false" /> <!-- Callback for deleting screenshot notification --> - <receiver android:name=".screenshot.GlobalScreenshot$DeleteScreenshotReceiver" + <receiver android:name=".screenshot.DeleteScreenshotReceiver" android:exported="false" /> <!-- Callback for invoking a smart action from the screenshot notification. --> - <receiver android:name=".screenshot.GlobalScreenshot$SmartActionsReceiver" + <receiver android:name=".screenshot.SmartActionsReceiver" android:exported="false"/> <!-- started from UsbDeviceSettingsManager --> diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml new file mode 100644 index 000000000000..827cf4a9d3b6 --- /dev/null +++ b/packages/SystemUI/res/drawable/privacy_chip_bg.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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="#242424" /> <!-- 14% of white --> + <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding" + android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" /> + <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 87dd58e4f0ed..213bb923db65 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.bubbles.BubbleManageEducationView +<com.android.systemui.bubbles.ManageEducationView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" @@ -87,4 +87,4 @@ /> </LinearLayout> </LinearLayout> -</com.android.systemui.bubbles.BubbleManageEducationView> +</com.android.systemui.bubbles.ManageEducationView> diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml new file mode 100644 index 000000000000..3c306322d21f --- /dev/null +++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml @@ -0,0 +1,40 @@ +<?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. +--> + + +<com.android.systemui.privacy.OngoingPrivacyChip + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/privacy_chip" + android:layout_height="match_parent" + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:focusable="true" > + + <FrameLayout + android:id="@+id/background" + android:layout_height="@dimen/ongoing_appops_chip_height" + android:layout_width="wrap_content" + android:minWidth="48dp" + android:layout_gravity="center_vertical"> + <LinearLayout + android:id="@+id/icons_container" + android:layout_height="match_parent" + android:layout_width="wrap_content" + android:gravity="center_vertical" + /> + </FrameLayout> +</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index be86e5f5abc5..3c7480181877 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -14,7 +14,7 @@ ** See the License for the specific language governing permissions and ** limitations under the License. --> -<FrameLayout +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res-auto" android:id="@+id/quick_status_bar_system_icons" @@ -27,6 +27,13 @@ android:clickable="true" android:paddingTop="@dimen/status_bar_padding_top" > + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="horizontal" + android:gravity="center_vertical|start" > + <com.android.systemui.statusbar.policy.Clock android:id="@+id/clock" android:layout_width="wrap_content" @@ -38,5 +45,23 @@ android:singleLine="true" android:textAppearance="@style/TextAppearance.StatusBar.Clock" systemui:showDark="false" /> + </LinearLayout> + + <android.widget.Space + android:id="@+id/space" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_gravity="center_vertical|center_horizontal" + android:visibility="gone" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="horizontal" + android:gravity="center_vertical|end" > + + <include layout="@layout/ongoing_privacy_chip" /> -</FrameLayout> + </LinearLayout> +</LinearLayout> diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 8e10230f2212..27bc3ab4aab0 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -1021,9 +1021,9 @@ <string name="priority_onboarding_settings_button_title" msgid="6663601574303585927">"Параметрлер"</string> <string name="magnification_window_title" msgid="4863914360847258333">"Ұлғайту терезесі"</string> <string name="magnification_controls_title" msgid="8421106606708891519">"Ұлғайту терезесінің басқару элементтері"</string> - <string name="quick_controls_title" msgid="6839108006171302273">"Құрылғы басқару виджеттері"</string> + <string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string> <string name="quick_controls_subtitle" msgid="1667408093326318053">"Жалғанған құрылғылар үшін басқару виджеттерін қосу"</string> - <string name="quick_controls_setup_title" msgid="8901436655997849822">"Құрылғы басқару виджеттерін реттеу"</string> + <string name="quick_controls_setup_title" msgid="8901436655997849822">"Құрылғыны басқару элементтерін реттеу"</string> <string name="quick_controls_setup_subtitle" msgid="1681506617879773824">"Басқару элементтерін шығару үшін қуат түймесін басып тұрыңыз."</string> <string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері енгізілетін қолданбаны таңдаңыз"</string> <plurals name="controls_number_of_favorites" formatted="false" msgid="1057347832073807380"> @@ -1046,7 +1046,7 @@ <string name="controls_favorite_load_error" msgid="5126216176144877419">"Басқару элементтері жүктелмеді. Қолданба параметрлерінің өзгермегенін тексеру үшін <xliff:g id="APP">%s</xliff:g> қолданбасын қараңыз."</string> <string name="controls_favorite_load_none" msgid="7687593026725357775">"Үйлесімді басқару элементтері қолжетімді емес."</string> <string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"Басқа"</string> - <string name="controls_dialog_title" msgid="2343565267424406202">"Құрылғы басқару виджеттеріне қосу"</string> + <string name="controls_dialog_title" msgid="2343565267424406202">"Құрылғы басқару элементтеріне қосу"</string> <string name="controls_dialog_ok" msgid="2770230012857881822">"Енгізу"</string> <string name="controls_dialog_message" msgid="342066938390663844">"<xliff:g id="APP">%s</xliff:g> ұсынған"</string> <string name="controls_dialog_confirmation" msgid="586517302736263447">"Басқару элементтері жаңартылды"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 6b8b674ea412..341efff0e4cd 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -811,7 +811,7 @@ <string name="keyboard_shortcut_group_system_notifications" msgid="3615971650562485878">"Notificações"</string> <string name="keyboard_shortcut_group_system_shortcuts_helper" msgid="4856808328618265589">"Atalhos de teclado"</string> <string name="keyboard_shortcut_group_system_switch_input" msgid="952555530383268166">"Alterar esquema de teclado"</string> - <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Aplicações"</string> + <string name="keyboard_shortcut_group_applications" msgid="7386239431100651266">"Apps"</string> <string name="keyboard_shortcut_group_applications_assist" msgid="771606231466098742">"Assistência"</string> <string name="keyboard_shortcut_group_applications_browser" msgid="2776211137869809251">"Navegador"</string> <string name="keyboard_shortcut_group_applications_contacts" msgid="2807268086386201060">"Contactos"</string> @@ -965,7 +965,7 @@ <string name="qs_dnd_until" msgid="7844269319043747955">"Até à(s) <xliff:g id="ID_1">%s</xliff:g>"</string> <string name="qs_dnd_keep" msgid="3829697305432866434">"Manter"</string> <string name="qs_dnd_replace" msgid="7712119051407052689">"Substituir"</string> - <string name="running_foreground_services_title" msgid="5137313173431186685">"Aplicações em execução em segundo plano"</string> + <string name="running_foreground_services_title" msgid="5137313173431186685">"Apps em execução em segundo plano"</string> <string name="running_foreground_services_msg" msgid="3009459259222695385">"Toque para obter detalhes acerca da utilização da bateria e dos dados"</string> <string name="mobile_data_disable_title" msgid="5366476131671617790">"Pretende desativar os dados móveis?"</string> <string name="mobile_data_disable_message" msgid="8604966027899770415">"Não terá acesso a dados ou à Internet através do operador <xliff:g id="CARRIER">%s</xliff:g>. A Internet estará disponível apenas por Wi-Fi."</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 27db8cb5fb46..f407a8dcc57f 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -499,6 +499,8 @@ <item>com.android.systemui</item> </string-array> + <integer name="ongoing_appops_dialog_max_apps">5</integer> + <!-- Launcher package name for overlaying icons. --> <string name="launcher_overlayable_package" translatable="false">com.android.launcher3</string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ea855eb722b9..122fcb21a9f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1137,6 +1137,23 @@ <!-- How much into a DisplayCutout's bounds we can go, on each side --> <dimen name="display_cutout_margin_consumption">0px</dimen> + + <!-- Height of the Ongoing App Ops chip --> + <dimen name="ongoing_appops_chip_height">32dp</dimen> + <!-- Padding between background of Ongoing App Ops chip and content --> + <dimen name="ongoing_appops_chip_bg_padding">8dp</dimen> + <!-- Side padding between background of Ongoing App Ops chip and content --> + <dimen name="ongoing_appops_chip_side_padding">8dp</dimen> + <!-- Margin between icons of Ongoing App Ops chip when QQS--> + <dimen name="ongoing_appops_chip_icon_margin_collapsed">0dp</dimen> + <!-- Margin between icons of Ongoing App Ops chip when QS--> + <dimen name="ongoing_appops_chip_icon_margin_expanded">2dp</dimen> + <!-- Icon size of Ongoing App Ops chip --> + <dimen name="ongoing_appops_chip_icon_size">@dimen/status_bar_icon_drawing_size</dimen> + <!-- Radius of Ongoing App Ops chip corners --> + <dimen name="ongoing_appops_chip_bg_corner_radius">16dp</dimen> + + <!-- How much each bubble is elevated. --> <dimen name="bubble_elevation">1dp</dimen> <!-- How much the bubble flyout text container is elevated. --> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index d97aea7dab59..8b6543ac73bd 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2596,6 +2596,27 @@ app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> <string name="heap_dump_tile_name">Dump SysUI Heap</string> + <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]--> + <string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string> + + <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]--> + <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string> + + <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] --> + <string name="ongoing_privacy_dialog_separator">,\u0020</string> + + <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] --> + <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string> + + <!-- Text for camera app op [CHAR LIMIT=20]--> + <string name="privacy_type_camera">camera</string> + + <!-- Text for location app op [CHAR LIMIT=20]--> + <string name="privacy_type_location">location</string> + + <!-- Text for microphone app op [CHAR LIMIT=20]--> + <string name="privacy_type_microphone">microphone</string> + <!-- Text for the quick setting tile for sensor privacy [CHAR LIMIT=30] --> <string name="sensor_privacy_mode">Sensors off</string> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 8a36e7b127db..878947f6ba37 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -287,11 +287,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final Executor mBackgroundExecutor; /** - * Short delay before restarting biometric authentication after a successful try - * This should be slightly longer than the time between on<biometric>Authenticated - * (e.g. onFingerprintAuthenticated) and setKeyguardGoingAway(true). + * Short delay before restarting fingerprint authentication after a successful try. This should + * be slightly longer than the time between onFingerprintAuthenticated and + * setKeyguardGoingAway(true). */ - private static final int BIOMETRIC_CONTINUE_DELAY_MS = 500; + private static final int FINGERPRINT_CONTINUE_DELAY_MS = 500; // If the HAL dies or is unable to authenticate, keyguard should retry after a short delay private int mHardwareFingerprintUnavailableRetryCount = 0; @@ -599,7 +599,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE), - BIOMETRIC_CONTINUE_DELAY_MS); + FINGERPRINT_CONTINUE_DELAY_MS); // Only authenticate fingerprint once when assistant is visible mAssistantVisible = false; @@ -782,9 +782,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE), - BIOMETRIC_CONTINUE_DELAY_MS); - // Only authenticate face once when assistant is visible mAssistantVisible = false; diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index 02d2b8e4ef0f..4dbb92e1e37f 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -54,6 +54,7 @@ import com.android.systemui.plugins.VolumeDialogController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.EnhancedEstimates; import com.android.systemui.power.PowerUI; +import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; import com.android.systemui.screenrecord.RecordingController; @@ -122,9 +123,9 @@ import com.android.systemui.util.leak.GarbageMonitor; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.leak.LeakReporter; import com.android.systemui.util.sensors.AsyncSensorManager; -import com.android.systemui.wm.DisplayController; -import com.android.systemui.wm.DisplayImeController; -import com.android.systemui.wm.SystemWindows; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.SystemWindows; import java.util.function.Consumer; @@ -294,6 +295,7 @@ public class Dependency { @Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager; @Inject Lazy<AutoHideController> mAutoHideController; @Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener; + @Inject Lazy<PrivacyItemController> mPrivacyItemController; @Inject @Background Lazy<Looper> mBgLooper; @Inject @Background Lazy<Handler> mBgHandler; @Inject @Main Lazy<Looper> mMainLooper; @@ -491,6 +493,7 @@ public class Dependency { mProviders.put(ForegroundServiceNotificationListener.class, mForegroundServiceNotificationListener::get); mProviders.put(ClockManager.class, mClockManager::get); + mProviders.put(PrivacyItemController.class, mPrivacyItemController::get); mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get); mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get); mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.java new file mode 100644 index 000000000000..769a344eedac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/DisplayIdIndexSupplier.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.systemui.accessibility; + +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.util.SparseArray; +import android.view.Display; + +import androidx.annotation.NonNull; + +/** + * Supplies the instance with given display Id. It generates a new instance if the corresponding + * one is not existed. It should run in single thread to avoid race conditions. + * + * @param <T> the type of results supplied by {@link #createInstance(Display)}. + */ +abstract class DisplayIdIndexSupplier<T> { + + private final SparseArray<T> mSparseArray = new SparseArray<>(); + private final DisplayManager mDisplayManager; + + /** + * @param displayManager DisplayManager + */ + DisplayIdIndexSupplier(DisplayManager displayManager) { + mDisplayManager = displayManager; + } + + /** + * @param displayId the logical display Id + * @return {@code null} if the given display id is invalid + */ + @Nullable + public T get(int displayId) { + T instance = mSparseArray.get(displayId); + if (instance != null) { + return instance; + } + final Display display = mDisplayManager.getDisplay(displayId); + if (display == null) { + return null; + } + instance = createInstance(display); + mSparseArray.put(displayId, instance); + return instance; + } + + @NonNull + protected abstract T createInstance(Display display); + + /** + * Removes the instance with given display Id. + * + * @param displayId the logical display id + */ + public void remove(int displayId) { + mSparseArray.remove(displayId); + } + + /** + * Clears all elements. + */ + public void clear() { + mSparseArray.clear(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java index 398a2c9c9d41..68a0a65ef50f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java @@ -30,6 +30,7 @@ import com.android.systemui.R; /** * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of * {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled. + * The button UI would automatically be dismissed after displaying for a period of time. */ class MagnificationModeSwitch { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java index e73ff13ceac1..ffc70bcf63d0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java @@ -19,31 +19,33 @@ package com.android.systemui.accessibility; import android.annotation.MainThread; import android.content.Context; import android.hardware.display.DisplayManager; -import android.util.Log; -import android.util.SparseArray; import android.view.Display; +import com.android.internal.annotations.VisibleForTesting; + import javax.inject.Singleton; /** - * Class to control magnification mode switch button. Shows the button UI when both full-screen - * and window magnification mode are capable, and when the magnification scale is changed. And - * the button UI would automatically be dismissed after displaying for a period of time. + * A class to control {@link MagnificationModeSwitch}. It should show the button UI with following + * conditions: + * <ol> + * <li> Both full-screen and window magnification mode are capable.</li> + * <li> The magnification scale is changed by a user.</li> + * <ol> */ @Singleton public class ModeSwitchesController { - private static final String TAG = "ModeSwitchesController"; - - private final Context mContext; - private final DisplayManager mDisplayManager; - - private final SparseArray<MagnificationModeSwitch> mDisplaysToSwitches = - new SparseArray<>(); + private final SwitchSupplier mSwitchSupplier; public ModeSwitchesController(Context context) { - mContext = context; - mDisplayManager = mContext.getSystemService(DisplayManager.class); + mSwitchSupplier = new SwitchSupplier(context, + context.getSystemService(DisplayManager.class)); + } + + @VisibleForTesting + ModeSwitchesController(SwitchSupplier switchSupplier) { + mSwitchSupplier = switchSupplier; } /** @@ -52,20 +54,17 @@ public class ModeSwitchesController { * * @param displayId The logical display id * @param mode The magnification mode - * * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW * @see android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN */ @MainThread void showButton(int displayId, int mode) { - if (mDisplaysToSwitches.get(displayId) == null) { - final MagnificationModeSwitch magnificationModeSwitch = - createMagnificationSwitchController(displayId); - if (magnificationModeSwitch == null) { - return; - } + final MagnificationModeSwitch magnificationModeSwitch = + mSwitchSupplier.get(displayId); + if (magnificationModeSwitch == null) { + return; } - mDisplaysToSwitches.get(displayId).showButton(mode); + magnificationModeSwitch.showButton(mode); } /** @@ -74,30 +73,34 @@ public class ModeSwitchesController { * @param displayId The logical display id */ void removeButton(int displayId) { - if (mDisplaysToSwitches.get(displayId) == null) { + final MagnificationModeSwitch magnificationModeSwitch = + mSwitchSupplier.get(displayId); + if (magnificationModeSwitch == null) { return; } - mDisplaysToSwitches.get(displayId).removeButton(); + magnificationModeSwitch.removeButton(); } - private MagnificationModeSwitch createMagnificationSwitchController(int displayId) { - if (mDisplayManager.getDisplay(displayId) == null) { - Log.w(TAG, "createMagnificationSwitchController displayId is invalid."); - return null; + @VisibleForTesting + static class SwitchSupplier extends DisplayIdIndexSupplier<MagnificationModeSwitch> { + + private final Context mContext; + + /** + * @param context Context + * @param displayManager DisplayManager + */ + SwitchSupplier(Context context, DisplayManager displayManager) { + super(displayManager); + mContext = context; } - final MagnificationModeSwitch - magnificationModeSwitch = new MagnificationModeSwitch( - getDisplayContext(displayId)); - mDisplaysToSwitches.put(displayId, magnificationModeSwitch); - return magnificationModeSwitch; - } - private Context getDisplayContext(int displayId) { - final Display display = mDisplayManager.getDisplay(displayId); - final Context context = (displayId == Display.DEFAULT_DISPLAY) - ? mContext - : mContext.createDisplayContext(display); - return context; + @Override + protected MagnificationModeSwitch createInstance(Display display) { + final Context context = (display.getDisplayId() == Display.DEFAULT_DISPLAY) + ? mContext + : mContext.createDisplayContext(display); + return new MagnificationModeSwitch(context); + } } - } diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java index 5fd7b53435cf..4df66602bb7e 100644 --- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java @@ -18,6 +18,7 @@ package com.android.systemui.appops; import android.app.AppOpsManager; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; @@ -25,11 +26,14 @@ import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; +import androidx.annotation.WorkerThread; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dump.DumpManager; +import com.android.systemui.util.Assert; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -62,6 +66,7 @@ public class AppOpsControllerImpl implements AppOpsController, private H mBGHandler; private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>(); private final SparseArray<Set<Callback>> mCallbacksByCode = new SparseArray<>(); + private final PermissionFlagsCache mFlagsCache; private boolean mListening; @GuardedBy("mActiveItems") @@ -82,8 +87,11 @@ public class AppOpsControllerImpl implements AppOpsController, public AppOpsControllerImpl( Context context, @Background Looper bgLooper, - DumpManager dumpManager) { + DumpManager dumpManager, + PermissionFlagsCache cache + ) { mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); + mFlagsCache = cache; mBGHandler = new H(bgLooper); final int numOps = OPS.length; for (int i = 0; i < numOps; i++) { @@ -230,10 +238,66 @@ public class AppOpsControllerImpl implements AppOpsController, } /** + * Does the app-op code refer to a user sensitive permission for the specified user id + * and package. Only user sensitive permission should be shown to the user by default. + * + * @param appOpCode The code of the app-op. + * @param uid The uid of the user. + * @param packageName The name of the package. + * + * @return {@code true} iff the app-op item is user sensitive + */ + private boolean isUserSensitive(int appOpCode, int uid, String packageName) { + String permission = AppOpsManager.opToPermission(appOpCode); + if (permission == null) { + return false; + } + int permFlags = mFlagsCache.getPermissionFlags(permission, + packageName, uid); + return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0; + } + + /** + * Does the app-op item refer to an operation that should be shown to the user. + * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive + * permission should be shown to the user by default. + * + * @param item The item + * + * @return {@code true} iff the app-op item should be shown to the user + */ + private boolean isUserVisible(AppOpItem item) { + return isUserVisible(item.getCode(), item.getUid(), item.getPackageName()); + } + + + /** + * Does the app-op, uid and package name, refer to an operation that should be shown to the + * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or + * ops that refer to user sensitive permission should be shown to the user by default. + * + * @param item The item + * + * @return {@code true} iff the app-op for should be shown to the user + */ + private boolean isUserVisible(int appOpCode, int uid, String packageName) { + // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission + // which may be user senstive, so for now always show it to the user. + if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) { + return true; + } + + return isUserSensitive(appOpCode, uid, packageName); + } + + /** * Returns a copy of the list containing all the active AppOps that the controller tracks. * + * Call from a worker thread as it may perform long operations. + * * @return List of active AppOps information */ + @WorkerThread public List<AppOpItem> getActiveAppOps() { return getActiveAppOpsForUser(UserHandle.USER_ALL); } @@ -242,18 +306,23 @@ public class AppOpsControllerImpl implements AppOpsController, * Returns a copy of the list containing all the active AppOps that the controller tracks, for * a given user id. * + * Call from a worker thread as it may perform long operations. + * * @param userId User id to track, can be {@link UserHandle#USER_ALL} * * @return List of active AppOps information for that user id */ + @WorkerThread public List<AppOpItem> getActiveAppOpsForUser(int userId) { + Assert.isNotMainThread(); List<AppOpItem> list = new ArrayList<>(); synchronized (mActiveItems) { final int numActiveItems = mActiveItems.size(); for (int i = 0; i < numActiveItems; i++) { AppOpItem item = mActiveItems.get(i); if ((userId == UserHandle.USER_ALL - || UserHandle.getUserId(item.getUid()) == userId)) { + || UserHandle.getUserId(item.getUid()) == userId) + && isUserVisible(item)) { list.add(item); } } @@ -263,7 +332,8 @@ public class AppOpsControllerImpl implements AppOpsController, for (int i = 0; i < numNotedItems; i++) { AppOpItem item = mNotedItems.get(i); if ((userId == UserHandle.USER_ALL - || UserHandle.getUserId(item.getUid()) == userId)) { + || UserHandle.getUserId(item.getUid()) == userId) + && isUserVisible(item)) { list.add(item); } } @@ -311,7 +381,7 @@ public class AppOpsControllerImpl implements AppOpsController, } private void notifySuscribers(int code, int uid, String packageName, boolean active) { - if (mCallbacksByCode.contains(code)) { + if (mCallbacksByCode.contains(code) && isUserVisible(code, uid, packageName)) { if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName); for (Callback cb: mCallbacksByCode.get(code)) { cb.onActiveStateChanged(code, uid, packageName, active); diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt new file mode 100644 index 000000000000..45ed78f750be --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt @@ -0,0 +1,88 @@ +/* + * 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.appops + +import android.content.pm.PackageManager +import android.os.UserHandle +import androidx.annotation.WorkerThread +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.Assert +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +private data class PermissionFlagKey( + val permission: String, + val packageName: String, + val uid: Int +) + +/** + * Cache for PackageManager's PermissionFlags. + * + * After a specific `{permission, package, uid}` has been requested, updates to it will be tracked, + * and changes to the uid will trigger new requests (in the background). + */ +@Singleton +class PermissionFlagsCache @Inject constructor( + private val packageManager: PackageManager, + @Background private val executor: Executor +) : PackageManager.OnPermissionsChangedListener { + + private val permissionFlagsCache = + mutableMapOf<Int, MutableMap<PermissionFlagKey, Int>>() + private var listening = false + + override fun onPermissionsChanged(uid: Int) { + executor.execute { + // Only track those that we've seen before + val keys = permissionFlagsCache.get(uid) + if (keys != null) { + keys.mapValuesTo(keys) { + getFlags(it.key) + } + } + } + } + + /** + * Retrieve permission flags from cache or PackageManager. There parameters will be passed + * directly to [PackageManager]. + * + * Calls to this method should be done from a background thread (though it will only be + * enforced if the cache is not hit). + */ + @WorkerThread + fun getPermissionFlags(permission: String, packageName: String, uid: Int): Int { + if (!listening) { + listening = true + packageManager.addOnPermissionsChangeListener(this) + } + val key = PermissionFlagKey(permission, packageName, uid) + return permissionFlagsCache.getOrPut(uid, { mutableMapOf() }).get(key) ?: run { + getFlags(key).also { + Assert.isNotMainThread() + permissionFlagsCache.get(uid)?.put(key, it) + } + } + } + + private fun getFlags(key: PermissionFlagKey): Int { + return packageManager.getPermissionFlags(key.permission, key.packageName, + UserHandle.getUserHandleForUid(key.uid)) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 980e4c0fd333..361ea674cead 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -29,7 +29,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.content.res.Configuration; import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; @@ -39,12 +38,10 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; -import android.hardware.fingerprint.IFingerprintService; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import android.view.WindowManager; @@ -247,6 +244,10 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, IActivityTaskManager getActivityTaskManager() { return ActivityTaskManager.getService(); } + + FingerprintManager getFingerprintManager(Context context) { + return context.getSystemService(FingerprintManager.class); + } } @Inject @@ -273,7 +274,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mActivityTaskManager = mInjector.getActivityTaskManager(); - final FingerprintManager fpm = mContext.getSystemService(FingerprintManager.class); + final FingerprintManager fpm = mInjector.getFingerprintManager(mContext); if (fpm != null && fpm.isHardwareDetected()) { final List<FingerprintSensorProperties> fingerprintSensorProperties = fpm.getSensorProperties(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java deleted file mode 100644 index 9db371e487c7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.bubbles; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.SpringAnimation; -import androidx.dynamicanimation.animation.SpringForce; - -import com.android.systemui.R; - -/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */ -public class BubbleDismissView extends FrameLayout { - /** Duration for animations involving the dismiss target text/icon. */ - private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150; - private static final float SCALE_FOR_POP = 1.2f; - private static final float SCALE_FOR_DISMISS = 0.9f; - - private LinearLayout mDismissTarget; - private ImageView mDismissIcon; - private View mDismissCircle; - - private SpringAnimation mDismissTargetAlphaSpring; - private SpringAnimation mDismissTargetVerticalSpring; - - public BubbleDismissView(Context context) { - super(context); - setVisibility(GONE); - - LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true); - mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container); - mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon); - mDismissCircle = findViewById(R.id.bubble_dismiss_circle); - - // Set up the basic target area animations. These are very simple animations that don't need - // fancy interpolators. - final AccelerateDecelerateInterpolator interpolator = - new AccelerateDecelerateInterpolator(); - mDismissIcon.animate() - .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION) - .setInterpolator(interpolator); - mDismissCircle.animate() - .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2) - .setInterpolator(interpolator); - - mDismissTargetAlphaSpring = - new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA) - .setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_LOW) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - mDismissTargetVerticalSpring = - new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y) - .setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); - - mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> { - // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being - // exactly zero when this listener is triggered. However, if it's less than 50% we can - // safely assume it was animating out rather than in. - if (alpha < 0.5f) { - // If the alpha spring was animating the view out, set it to GONE when it's done. - setVisibility(INVISIBLE); - } - }); - } - - /** Springs in the dismiss target. */ - void springIn() { - setVisibility(View.VISIBLE); - - // Fade in the dismiss target icon. - mDismissIcon.animate() - .setDuration(50) - .scaleX(1f) - .scaleY(1f) - .alpha(1f); - mDismissTarget.setAlpha(0f); - mDismissTargetAlphaSpring.animateToFinalPosition(1f); - - // Spring up the dismiss target. - mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f); - mDismissTargetVerticalSpring.animateToFinalPosition(0); - - mDismissCircle.setAlpha(0f); - mDismissCircle.setScaleX(SCALE_FOR_POP); - mDismissCircle.setScaleY(SCALE_FOR_POP); - - // Fade in circle and reduce size. - mDismissCircle.animate() - .alpha(1f) - .scaleX(1f) - .scaleY(1f); - } - - /** Springs out the dismiss target. */ - void springOut() { - // Fade out the target icon. - mDismissIcon.animate() - .setDuration(50) - .scaleX(SCALE_FOR_DISMISS) - .scaleY(SCALE_FOR_DISMISS) - .alpha(0f); - - // Fade out the target. - mDismissTargetAlphaSpring.animateToFinalPosition(0f); - - // Spring the target down a bit. - mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f); - - // Pop out the circle. - mDismissCircle.animate() - .scaleX(SCALE_FOR_DISMISS) - .scaleY(SCALE_FOR_DISMISS) - .alpha(0f); - } - - /** Returns the Y value of the center of the dismiss target. */ - float getDismissTargetCenterY() { - return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f; - } - - /** Returns the dismiss target, which contains the text/icon and any added padding. */ - View getDismissTarget() { - return mDismissTarget; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java deleted file mode 100644 index 86244ba5248a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleManageEducationView.java +++ /dev/null @@ -1,106 +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.bubbles; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.internal.util.ContrastColorUtil; -import com.android.systemui.R; - -/** - * Educational view to highlight the manage button that allows a user to configure the settings - * for the bubble. Shown only the first time a user expands a bubble. - */ -public class BubbleManageEducationView extends LinearLayout { - - private View mManageView; - private TextView mTitleTextView; - private TextView mDescTextView; - - public BubbleManageEducationView(Context context) { - this(context, null); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr) { - this(context, attrs, defStyleAttr, 0); - } - - public BubbleManageEducationView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - - mManageView = findViewById(R.id.manage_education_view); - mTitleTextView = findViewById(R.id.user_education_title); - mDescTextView = findViewById(R.id.user_education_description); - - final TypedArray ta = mContext.obtainStyledAttributes( - new int[] {android.R.attr.colorAccent, - android.R.attr.textColorPrimaryInverse}); - final int bgColor = ta.getColor(0, Color.BLACK); - int textColor = ta.getColor(1, Color.WHITE); - ta.recycle(); - - textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true); - mTitleTextView.setTextColor(textColor); - mDescTextView.setTextColor(textColor); - } - - /** - * Specifies the position for the manage view. - */ - public void setManageViewPosition(int x, int y) { - mManageView.setTranslationX(x); - mManageView.setTranslationY(y); - } - - /** - * @return the height of the view that shows the educational text and pointer. - */ - public int getManageViewHeight() { - return mManageView.getHeight(); - } - - @Override - public void setLayoutDirection(int direction) { - super.setLayoutDirection(direction); - if (getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) { - mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg_rtl); - mTitleTextView.setGravity(Gravity.RIGHT); - mDescTextView.setGravity(Gravity.RIGHT); - } else { - mManageView.setBackgroundResource(R.drawable.bubble_stack_user_education_bg); - mTitleTextView.setGravity(Gravity.LEFT); - mDescTextView.setGravity(Gravity.LEFT); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java deleted file mode 100644 index bb9d1095a37a..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java +++ /dev/null @@ -1,179 +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.bubbles; - -import static android.view.Display.INVALID_DISPLAY; -import static android.view.View.GONE; - -import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; - -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Matrix; -import android.graphics.Path; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.InsetDrawable; -import android.util.PathParser; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import com.android.systemui.R; - -/** - * Class for showing aged out bubbles. - */ -public class BubbleOverflow implements BubbleViewProvider { - public static final String KEY = "Overflow"; - - private BadgedImageView mOverflowBtn; - private BubbleExpandedView mExpandedView; - private LayoutInflater mInflater; - private Context mContext; - private Bitmap mIcon; - private Path mPath; - private int mBitmapSize; - private int mIconBitmapSize; - private int mDotColor; - - public BubbleOverflow(Context context) { - mContext = context; - mInflater = LayoutInflater.from(context); - } - - void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) { - updateDimensions(); - mExpandedView = (BubbleExpandedView) mInflater.inflate( - R.layout.bubble_expanded_view, parentViewGroup /* root */, - false /* attachToRoot */); - mExpandedView.setOverflow(true); - mExpandedView.setStackView(stackView); - mExpandedView.applyThemeAttrs(); - updateIcon(mContext, parentViewGroup); - } - - void updateDimensions() { - mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size); - mIconBitmapSize = mContext.getResources().getDimensionPixelSize( - R.dimen.bubble_overflow_icon_bitmap_size); - if (mExpandedView != null) { - mExpandedView.updateDimensions(); - } - } - - void updateIcon(Context context, ViewGroup parentViewGroup) { - mContext = context; - mInflater = LayoutInflater.from(context); - mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button, - parentViewGroup /* root */, - false /* attachToRoot */); - mOverflowBtn.setContentDescription(mContext.getResources().getString( - R.string.bubble_overflow_button_content_description)); - Resources res = mContext.getResources(); - - // Set color for button icon and dot - TypedValue typedValue = new TypedValue(); - mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true); - int colorAccent = mContext.getColor(typedValue.resourceId); - mOverflowBtn.getDrawable().setTint(colorAccent); - mDotColor = colorAccent; - - // Set color for button and activity background - ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light)); - final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - if (mode == Configuration.UI_MODE_NIGHT_YES) { - bg = new ColorDrawable(res.getColor(R.color.bubbles_dark)); - } - - // Apply icon inset - InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), - mBitmapSize - mIconBitmapSize /* inset */); - AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg); - - BubbleIconFactory iconFactory = new BubbleIconFactory(mContext); - mIcon = iconFactory.createBadgedIconBitmap(adaptiveIconDrawable, - null /* user */, - true /* shrinkNonAdaptiveIcons */).icon; - - // Get path with dot location - float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(), - null /* outBounds */, null /* path */, null /* outMaskShape */); - float radius = DEFAULT_PATH_SIZE / 2f; - mPath = PathParser.createPathFromPathData( - mContext.getResources().getString(com.android.internal.R.string.config_icon_mask)); - Matrix matrix = new Matrix(); - matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, - radius /* pivot y */); - mPath.transform(matrix); - - mOverflowBtn.setRenderedBubble(this); - } - - void setVisible(int visible) { - mOverflowBtn.setVisibility(visible); - } - - @Override - public BubbleExpandedView getExpandedView() { - return mExpandedView; - } - - @Override - public int getDotColor() { - return mDotColor; - } - - @Override - public Bitmap getBadgedImage() { - return mIcon; - } - - @Override - public boolean showDot() { - return false; - } - - @Override - public Path getDotPath() { - return mPath; - } - - @Override - public void setContentVisibility(boolean visible) { - mExpandedView.setContentVisibility(visible); - } - - @Override - public View getIconView() { - return mOverflowBtn; - } - - @Override - public String getKey() { - return BubbleOverflow.KEY; - } - - @Override - public int getDisplayId() { - return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt new file mode 100644 index 000000000000..155b71b99ff9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt @@ -0,0 +1,163 @@ +/* + * 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.bubbles + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Matrix +import android.graphics.Path +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.InsetDrawable +import android.util.PathParser +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import com.android.systemui.R + +class BubbleOverflow( + private val context: Context, + private val stack: BubbleStackView +) : BubbleViewProvider { + + private var bitmap: Bitmap? = null + private var dotPath: Path? = null + private var bitmapSize = 0 + private var iconBitmapSize = 0 + private var dotColor = 0 + + private val inflater: LayoutInflater = LayoutInflater.from(context) + private val expandedView: BubbleExpandedView = inflater + .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */) + as BubbleExpandedView + private val overflowBtn: BadgedImageView = inflater + .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */) + as BadgedImageView + init { + updateResources() + with(expandedView) { + setOverflow(true) + setStackView(stack) + applyThemeAttrs() + } + with(overflowBtn) { + setContentDescription(context.resources.getString( + R.string.bubble_overflow_button_content_description)) + updateBtnTheme() + } + } + + fun update() { + updateResources() + expandedView.applyThemeAttrs() + // Apply inset and new style to fresh icon drawable. + overflowBtn.setImageResource(R.drawable.ic_bubble_overflow_button) + updateBtnTheme() + } + + fun updateResources() { + bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size) + iconBitmapSize = context.resources.getDimensionPixelSize( + R.dimen.bubble_overflow_icon_bitmap_size) + val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size) + overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize)) + expandedView.updateDimensions() + } + + fun updateBtnTheme() { + val res = context.resources + + // Set overflow button accent color, dot color + val typedValue = TypedValue() + context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true) + + val colorAccent = res.getColor(typedValue.resourceId) + overflowBtn.getDrawable()?.setTint(colorAccent) + dotColor = colorAccent + + // Set button and activity background color + val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + == Configuration.UI_MODE_NIGHT_YES) + val bg = ColorDrawable(res.getColor( + if (nightMode) R.color.bubbles_dark else R.color.bubbles_light)) + + // Set button icon + val iconFactory = BubbleIconFactory(context) + val fg = InsetDrawable(overflowBtn.getDrawable(), + bitmapSize - iconBitmapSize /* inset */) + bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg), + null /* user */, true /* shrinkNonAdaptiveIcons */).icon + + // Set dot path + dotPath = PathParser.createPathFromPathData( + res.getString(com.android.internal.R.string.config_icon_mask)) + val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(), + null /* outBounds */, null /* path */, null /* outMaskShape */) + val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f + val matrix = Matrix() + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */) + dotPath?.transform(matrix) + overflowBtn.setRenderedBubble(this) + } + + fun setVisible(visible: Int) { + overflowBtn.visibility = visible + } + + override fun getExpandedView(): BubbleExpandedView? { + return expandedView + } + + override fun getDotColor(): Int { + return dotColor + } + + override fun getBadgedImage(): Bitmap? { + return bitmap + } + + override fun showDot(): Boolean { + return false + } + + override fun getDotPath(): Path? { + return dotPath + } + + override fun setContentVisibility(visible: Boolean) { + expandedView.setContentVisibility(visible) + } + + override fun getIconView(): View? { + return overflowBtn + } + + override fun getKey(): String { + return KEY + } + + override fun getDisplayId(): Int { + return expandedView.virtualDisplayId + } + + companion object { + @JvmField val KEY = "Overflow" + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index f02945ef843a..ea12c9598b91 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -48,7 +48,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; -import android.graphics.drawable.TransitionDrawable; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; @@ -95,7 +94,6 @@ 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.CollapsedStatusBarFragment; -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; @@ -118,7 +116,7 @@ public class BubbleStackView extends FrameLayout private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES; /** Animation durations for bubble stack user education views. **/ - private static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200; + static final int ANIMATE_STACK_USER_EDUCATION_DURATION = 200; private static final int ANIMATE_STACK_USER_EDUCATION_DURATION_SHORT = 40; /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */ @@ -139,9 +137,6 @@ public class BubbleStackView extends FrameLayout /** Percent to darken the bubbles when they're in the dismiss target. */ private static final float DARKEN_PERCENT = 0.3f; - /** Duration of the dismiss scrim fading in/out. */ - private static final int DISMISS_TRANSITION_DURATION_MS = 200; - /** How long to wait, in milliseconds, before hiding the flyout. */ @VisibleForTesting static final int FLYOUT_HIDE_AFTER = 5000; @@ -300,7 +295,7 @@ public class BubbleStackView extends FrameLayout public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("Stack view state:"); pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress); - pw.print(" showingDismiss: "); pw.println(mShowingDismiss); + pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing()); pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating); pw.print(" expandedContainerVis: "); pw.println(mExpandedViewContainer.getVisibility()); pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha()); @@ -347,7 +342,6 @@ public class BubbleStackView extends FrameLayout private boolean mViewUpdatedRequested = false; private boolean mIsExpansionAnimating = false; private boolean mIsBubbleSwitchAnimating = false; - private boolean mShowingDismiss = false; /** The view to desaturate/darken when magneted to the dismiss target. */ @Nullable private View mDesaturateAndDarkenTargetView; @@ -465,7 +459,7 @@ public class BubbleStackView extends FrameLayout if (wasFlungOut) { mExpandedAnimationController.snapBubbleBack( mExpandedAnimationController.getDraggedOutBubble(), velX, velY); - hideDismissTarget(); + mDismissView.hide(); } else { mExpandedAnimationController.onUnstuckFromTarget(); } @@ -479,9 +473,9 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dismissDraggedOutBubble( mExpandedAnimationController.getDraggedOutBubble() /* bubble */, - mDismissTargetContainer.getHeight() /* translationYBy */, + mDismissView.getHeight() /* translationYBy */, BubbleStackView.this::dismissMagnetizedObject /* after */); - hideDismissTarget(); + mDismissView.hide(); } }; @@ -502,7 +496,7 @@ public class BubbleStackView extends FrameLayout if (wasFlungOut) { mStackAnimationController.flingStackThenSpringToEdge( mStackAnimationController.getStackPosition().x, velX, velY); - hideDismissTarget(); + mDismissView.hide(); } else { mStackAnimationController.onUnstuckFromTarget(); } @@ -511,14 +505,14 @@ public class BubbleStackView extends FrameLayout @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { mStackAnimationController.animateStackDismissal( - mDismissTargetContainer.getHeight() /* translationYBy */, + mDismissView.getHeight() /* translationYBy */, () -> { resetDesaturationAndDarken(); dismissMagnetizedObject(); } ); - hideDismissTarget(); + mDismissView.hide(); } }; @@ -639,7 +633,7 @@ public class BubbleStackView extends FrameLayout } // Show the dismiss target, if we haven't already. - springInDismissTargetMaybe(); + mDismissView.show(); // First, see if the magnetized object consumes the event - if so, we shouldn't move the // bubble since it's stuck to the target. @@ -681,7 +675,7 @@ public class BubbleStackView extends FrameLayout SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED); } - hideDismissTarget(); + mDismissView.hide(); } mIsDraggingStack = false; @@ -743,12 +737,7 @@ public class BubbleStackView extends FrameLayout } }; - private View mDismissTargetCircle; - private ViewGroup mDismissTargetContainer; - private PhysicsAnimator<View> mDismissTargetAnimator; - private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig( - SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); - + private DismissView mDismissView; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; @Nullable @@ -759,7 +748,7 @@ public class BubbleStackView extends FrameLayout private View mUserEducationView; private boolean mShouldShowManageEducation; - private BubbleManageEducationView mManageEducationView; + private ManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; private ViewGroup mManageMenu; @@ -866,34 +855,8 @@ public class BubbleStackView extends FrameLayout .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring); - final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); - mDismissTargetCircle = new DismissCircleView(context); - final FrameLayout.LayoutParams newParams = - new FrameLayout.LayoutParams(targetSize, targetSize); - newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - mDismissTargetCircle.setLayoutParams(newParams); - mDismissTargetAnimator = PhysicsAnimator.getInstance(mDismissTargetCircle); - - mDismissTargetContainer = new FrameLayout(context); - mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams( - MATCH_PARENT, - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), - Gravity.BOTTOM)); - - final int bottomMargin = - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin); - mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin); - mDismissTargetContainer.setClipToPadding(false); - mDismissTargetContainer.setClipChildren(false); - mDismissTargetContainer.addView(mDismissTargetCircle); - mDismissTargetContainer.setVisibility(View.INVISIBLE); - mDismissTargetContainer.setBackgroundResource( - R.drawable.floating_dismiss_gradient_transition); - addView(mDismissTargetContainer); - - // Start translated down so the target springs up. - mDismissTargetCircle.setTranslationY( - getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height)); + mDismissView = new DismissView(context); + addView(mDismissView); final ContentResolver contentResolver = getContext().getContentResolver(); final int dismissRadius = Settings.Secure.getInt( @@ -901,13 +864,23 @@ public class BubbleStackView extends FrameLayout // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects. - mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius); + mMagneticTarget = new MagnetizedObject.MagneticTarget( + mDismissView.getCircle(), dismissRadius); setClipChildren(false); setFocusable(true); mBubbleContainer.bringToFront(); - setUpOverflow(); + mBubbleOverflow = new BubbleOverflow(getContext(), this); + mBubbleContainer.addView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() /* index */, + new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + updateOverflow(); + mBubbleOverflow.getIconView().setOnClickListener((View v) -> { + setSelectedBubble(mBubbleOverflow); + showManageMenu(false); + }); mOnImeVisibilityChanged = onImeVisibilityChanged; mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback; @@ -933,7 +906,7 @@ public class BubbleStackView extends FrameLayout (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - mBubbleOverflow.updateDimensions(); + mBubbleOverflow.updateResources(); // Need to update the padding around the view WindowInsets insets = getRootWindowInsets(); @@ -1162,12 +1135,9 @@ public class BubbleStackView extends FrameLayout Log.d(TAG, "shouldShowManageEducation: " + mShouldShowManageEducation); } if (mShouldShowManageEducation) { - mManageEducationView = (BubbleManageEducationView) - mInflater.inflate(R.layout.bubbles_manage_button_education, this, + mManageEducationView = (ManageEducationView) + mInflater.inflate(R.layout.bubbles_manage_button_education, this /* root */, false /* attachToRoot */); - mManageEducationView.setVisibility(GONE); - mManageEducationView.setElevation(mBubbleElevation); - mManageEducationView.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); addView(mManageEducationView); } } @@ -1187,32 +1157,21 @@ public class BubbleStackView extends FrameLayout addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); } - private void setUpOverflow() { - int overflowBtnIndex = 0; - if (mBubbleOverflow == null) { - mBubbleOverflow = new BubbleOverflow(getContext()); - mBubbleOverflow.setUpOverflow(mBubbleContainer, this); - } else { - mBubbleContainer.removeView(mBubbleOverflow.getIconView()); - mBubbleOverflow.setUpOverflow(mBubbleContainer, this); - overflowBtnIndex = mBubbleContainer.getChildCount(); - } - mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - mBubbleOverflow.getIconView().setOnClickListener(v -> { - setSelectedBubble(mBubbleOverflow); - showManageMenu(false); - }); + private void updateOverflow() { + mBubbleOverflow.update(); + mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() - 1 /* index */); updateOverflowVisibility(); } + /** * Handle theme changes. */ public void onThemeChanged() { setUpFlyout(); - setUpOverflow(); setUpUserEducation(); setUpManageMenu(); + updateOverflow(); updateExpandedViewTheme(); } @@ -1261,7 +1220,7 @@ public class BubbleStackView extends FrameLayout /** Respond to the display size change by recalculating view size and location. */ public void onDisplaySizeChanged() { - setUpOverflow(); + updateOverflow(); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); wm.getDefaultDisplay().getRealSize(mDisplaySize); @@ -1279,12 +1238,7 @@ public class BubbleStackView extends FrameLayout } mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - - final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size); - mDismissTargetCircle.getLayoutParams().width = targetSize; - mDismissTargetCircle.getLayoutParams().height = targetSize; - mDismissTargetCircle.requestLayout(); - + mDismissView.updateResources(); mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2); } @@ -1796,28 +1750,8 @@ public class BubbleStackView extends FrameLayout && mManageEducationView.getVisibility() != VISIBLE && mIsExpanded && mExpandedBubble.getExpandedView() != null) { - mManageEducationView.setAlpha(0); - mManageEducationView.setVisibility(VISIBLE); - mManageEducationView.post(() -> { - mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); - final int viewHeight = mManageEducationView.getManageViewHeight(); - final int inset = getResources().getDimensionPixelSize( - R.dimen.bubbles_manage_education_top_inset); - mManageEducationView.bringToFront(); - mManageEducationView.setManageViewPosition(0, mTempRect.top - viewHeight + inset); - mManageEducationView.animate() - .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION) - .setInterpolator(FAST_OUT_SLOW_IN).alpha(1); - mManageEducationView.findViewById(R.id.manage).setOnClickListener(view -> { - mExpandedBubble.getExpandedView().findViewById(R.id.settings_button) - .performClick(); - maybeShowManageEducation(false); - }); - mManageEducationView.findViewById(R.id.got_it).setOnClickListener(view -> - maybeShowManageEducation(false)); - mManageEducationView.setOnClickListener(view -> - maybeShowManageEducation(false)); - }); + mManageEducationView.show(mExpandedBubble.getExpandedView(), mTempRect, + () -> maybeShowManageEducation(false) /* run on click */); Prefs.putBoolean(getContext(), HAS_SEEN_BUBBLES_MANAGE_EDUCATION, true); } else if (!show && mManageEducationView.getVisibility() == VISIBLE @@ -2362,48 +2296,6 @@ public class BubbleStackView extends FrameLayout } } - /** Animates in the dismiss target. */ - private void springInDismissTargetMaybe() { - if (mShowingDismiss) { - return; - } - - mShowingDismiss = true; - - mDismissTargetContainer.bringToFront(); - mDismissTargetContainer.setZ(Short.MAX_VALUE - 1); - mDismissTargetContainer.setVisibility(VISIBLE); - - ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition( - DISMISS_TRANSITION_DURATION_MS); - - mDismissTargetAnimator.cancel(); - mDismissTargetAnimator - .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring) - .start(); - } - - /** - * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they - * were dragged into the target and encircled. - */ - private void hideDismissTarget() { - if (!mShowingDismiss) { - return; - } - - mShowingDismiss = false; - - ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition( - DISMISS_TRANSITION_DURATION_MS); - - mDismissTargetAnimator - .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(), - mDismissTargetSpring) - .withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE)) - .start(); - } - /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */ private void animateFlyoutCollapsed(boolean collapsed, float velX) { final boolean onLeft = mStackAnimationController.isStackOnLeftSide(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt new file mode 100644 index 000000000000..71faf4a2eeb7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt @@ -0,0 +1,85 @@ +package com.android.systemui.bubbles + +import android.content.Context +import android.graphics.drawable.TransitionDrawable +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.dynamicanimation.animation.DynamicAnimation +import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY +import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW +import com.android.systemui.R +import com.android.systemui.util.DismissCircleView +import com.android.systemui.util.animation.PhysicsAnimator + +/* + * View that handles interactions between DismissCircleView and BubbleStackView. + */ +class DismissView(context: Context) : FrameLayout(context) { + + var circle = DismissCircleView(context).apply { + val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + val newParams = LayoutParams(targetSize, targetSize) + newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + setLayoutParams(newParams) + setTranslationY( + resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat()) + } + + var isShowing = false + private val animator = PhysicsAnimator.getInstance(circle) + private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY); + private val DISMISS_SCRIM_FADE_MS = 200 + init { + setLayoutParams(LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height), + Gravity.BOTTOM)) + setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin)) + setClipToPadding(false) + setClipChildren(false) + setVisibility(View.INVISIBLE) + setBackgroundResource( + R.drawable.floating_dismiss_gradient_transition) + addView(circle) + } + + /** + * Animates this view in. + */ + fun show() { + if (isShowing) return + isShowing = true + bringToFront() + setZ(Short.MAX_VALUE - 1f) + setVisibility(View.VISIBLE) + (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS) + animator.cancel() + animator + .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring) + .start() + } + + /** + * Animates this view out, as well as the circle that encircles the bubbles, if they + * were dragged into the target and encircled. + */ + fun hide() { + if (!isShowing) return + isShowing = false + (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS) + animator + .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(), + spring) + .withEndActions({ setVisibility(View.INVISIBLE) }) + .start() + } + + fun updateResources() { + val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size) + circle.layoutParams.width = targetSize + circle.layoutParams.height = targetSize + circle.requestLayout() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt new file mode 100644 index 000000000000..c58ab31c4561 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt @@ -0,0 +1,117 @@ +/* + * 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.bubbles + +import android.content.Context +import android.graphics.Color +import android.graphics.Rect +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.Button +import android.widget.LinearLayout +import android.widget.TextView +import com.android.internal.util.ContrastColorUtil +import com.android.systemui.Interpolators +import com.android.systemui.R + +/** + * Educational view to highlight the manage button that allows a user to configure the settings + * for the bubble. Shown only the first time a user expands a bubble. + */ +class ManageEducationView @JvmOverloads constructor( + context: Context?, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + defStyleRes: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { + + private val manageView by lazy { findViewById<View>(R.id.manage_education_view) } + private val manageButton by lazy { findViewById<Button>(R.id.manage) } + private val gotItButton by lazy { findViewById<Button>(R.id.got_it) } + private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) } + private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) } + private var isInflated = false + + init { + this.visibility = View.GONE + this.elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat() + this.layoutDirection = View.LAYOUT_DIRECTION_LOCALE + } + + override fun setLayoutDirection(direction: Int) { + super.setLayoutDirection(direction) + // setLayoutDirection runs before onFinishInflate + // so skip if views haven't inflated; otherwise we'll get NPEs + if (!isInflated) return + setDirection() + } + + override fun onFinishInflate() { + super.onFinishInflate() + isInflated = true + setDirection() + setTextColor() + } + + private fun setTextColor() { + val typedArray = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent, + android.R.attr.textColorPrimaryInverse)) + val bgColor = typedArray.getColor(0 /* index */, Color.BLACK) + var textColor = typedArray.getColor(1 /* index */, Color.WHITE) + typedArray.recycle() + textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true) + titleTextView.setTextColor(textColor) + descTextView.setTextColor(textColor) + } + + fun setDirection() { + manageView.setBackgroundResource( + if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) + R.drawable.bubble_stack_user_education_bg_rtl + else R.drawable.bubble_stack_user_education_bg) + titleTextView.gravity = Gravity.START + descTextView.gravity = Gravity.START + } + + fun show(expandedView: BubbleExpandedView, rect : Rect, hideMenu: Runnable) { + alpha = 0f + visibility = View.VISIBLE + post { + expandedView.getManageButtonBoundsOnScreen(rect) + with(hideMenu) { + manageButton + .setOnClickListener { + expandedView.findViewById<View>(R.id.settings_button).performClick() + this.run() + } + gotItButton.setOnClickListener { this.run() } + setOnClickListener { this.run() } + } + with(manageView) { + translationX = 0f + val inset = resources.getDimensionPixelSize( + R.dimen.bubbles_manage_education_top_inset) + translationY = (rect.top - manageView.height + inset).toFloat() + } + bringToFront() + animate() + .setDuration(BubbleStackView.ANIMATE_STACK_USER_EDUCATION_DURATION.toLong()) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .alpha(1f) + } + } +}
\ No newline at end of file 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 58807f0f7025..aa3e193ddba2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -49,6 +49,11 @@ open class ControlsBindingControllerImpl @Inject constructor( private const val SUGGESTED_STRUCTURES = 6L private const val SUGGESTED_CONTROLS_REQUEST = ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE * SUGGESTED_STRUCTURES + + private val emptyCallback = object : ControlsBindingController.LoadCallback { + override fun accept(controls: List<Control>) {} + override fun error(message: String) {} + } } private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) @@ -283,7 +288,7 @@ open class ControlsBindingControllerImpl @Inject constructor( } private inner class LoadSubscriber( - val callback: ControlsBindingController.LoadCallback, + var callback: ControlsBindingController.LoadCallback, val requestLimit: Long ) : IControlsSubscriber.Stub() { val loadedControls = ArrayList<Control>() @@ -337,6 +342,10 @@ open class ControlsBindingControllerImpl @Inject constructor( if (isTerminated.get()) return _loadCancelInternal = {} + + // Reassign the callback to clear references to other areas of code. Binders such as + // this may not be GC'd right away, so do not hold onto these references. + callback = emptyCallback currentProvider?.cancelLoadTimeout() backgroundExecutor.execute { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java index 56d0fa237b82..6e8d63b2c516 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java @@ -18,7 +18,9 @@ package com.android.systemui.dagger; import android.content.BroadcastReceiver; -import com.android.systemui.screenshot.GlobalScreenshot.ActionProxyReceiver; +import com.android.systemui.screenshot.ActionProxyReceiver; +import com.android.systemui.screenshot.DeleteScreenshotReceiver; +import com.android.systemui.screenshot.SmartActionsReceiver; import dagger.Binds; import dagger.Module; @@ -30,10 +32,31 @@ import dagger.multibindings.IntoMap; */ @Module public abstract class DefaultBroadcastReceiverBinder { - /** */ + /** + * + */ @Binds @IntoMap @ClassKey(ActionProxyReceiver.class) public abstract BroadcastReceiver bindActionProxyReceiver( ActionProxyReceiver broadcastReceiver); + + /** + * + */ + @Binds + @IntoMap + @ClassKey(DeleteScreenshotReceiver.class) + public abstract BroadcastReceiver bindDeleteScreenshotReceiver( + DeleteScreenshotReceiver broadcastReceiver); + + /** + * + */ + @Binds + @IntoMap + @ClassKey(SmartActionsReceiver.class) + public abstract BroadcastReceiver bindSmartActionsReceiver( + SmartActionsReceiver broadcastReceiver); + } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index cd0ba290db46..803e56db8ff3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl; import com.android.systemui.statusbar.policy.HeadsUpManager; +import com.android.systemui.wmshell.WindowManagerShellModule; import javax.inject.Named; import javax.inject.Singleton; @@ -71,7 +72,7 @@ import dagger.Provides; * A dagger module for injecting default implementations of components of System UI that may be * overridden by the System UI implementation. */ -@Module(includes = {DividerModule.class, QSModule.class}) +@Module(includes = {DividerModule.class, QSModule.class, WindowManagerShellModule.class}) public abstract class SystemUIDefaultModule { @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 4bd046e23dab..fce545b421d5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.ConcurrencyModule; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.SensorModule; +import com.android.systemui.util.settings.SettingsUtilModule; import com.android.systemui.util.time.SystemClock; import com.android.systemui.util.time.SystemClockImpl; @@ -65,7 +66,8 @@ import dagger.Provides; LogModule.class, PeopleHubModule.class, SensorModule.class, - SettingsModule.class + SettingsModule.class, + SettingsUtilModule.class }, subcomponents = {StatusBarComponent.class, NotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java index b9d23ade2ee1..1ef806c8bd68 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java @@ -364,9 +364,6 @@ public class DozeMachine { Log.i(TAG, "Dropping pulse done because current state is already done: " + mState); return mState; } - if (requestedState == State.DOZE_AOD && mBatteryController.isAodPowerSave()) { - return State.DOZE; - } if (requestedState == State.DOZE_REQUEST_PULSE && !mState.canPulse()) { Log.i(TAG, "Dropping pulse request because current state can't pulse: " + mState); return mState; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 37bdda8a06a1..524d9c8536b8 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -21,7 +21,6 @@ import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_ import android.annotation.AnyThread; import android.app.ActivityManager; -import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.hardware.Sensor; @@ -49,6 +48,7 @@ import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; @@ -64,10 +64,10 @@ public class DozeSensors { private final Context mContext; private final AsyncSensorManager mSensorManager; - private final ContentResolver mResolver; private final AmbientDisplayConfiguration mConfig; private final WakeLock mWakeLock; private final Consumer<Boolean> mProxCallback; + private final SecureSettings mSecureSettings; private final Callback mCallback; @VisibleForTesting protected TriggerSensor[] mSensors; @@ -98,13 +98,13 @@ public class DozeSensors { DozeSensors(Context context, AsyncSensorManager sensorManager, DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock, Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog, - ProximitySensor proximitySensor) { + ProximitySensor proximitySensor, SecureSettings secureSettings) { mContext = context; mSensorManager = sensorManager; mConfig = config; mWakeLock = wakeLock; mProxCallback = proxCallback; - mResolver = mContext.getContentResolver(); + mSecureSettings = secureSettings; mCallback = callback; mProximitySensor = proximitySensor; @@ -241,7 +241,7 @@ public class DozeSensors { } if (!anyListening) { - mResolver.unregisterContentObserver(mSettingsObserver); + mSecureSettings.unregisterContentObserver(mSettingsObserver); } else if (!mSettingRegistered) { for (TriggerSensor s : mSensors) { s.registerSettingsObserver(mSettingsObserver); @@ -400,7 +400,7 @@ public class DozeSensors { } else if (TextUtils.isEmpty(mSetting)) { return true; } - return Settings.Secure.getIntForUser(mResolver, mSetting, mSettingDefault ? 1 : 0, + return mSecureSettings.getIntForUser(mSetting, mSettingDefault ? 1 : 0, UserHandle.USER_CURRENT) != 0; } @@ -444,9 +444,8 @@ public class DozeSensors { public void registerSettingsObserver(ContentObserver settingsObserver) { if (mConfigured && !TextUtils.isEmpty(mSetting)) { - mResolver.registerContentObserver( - Settings.Secure.getUriFor(mSetting), false /* descendants */, - mSettingsObserver, UserHandle.USER_ALL); + mSecureSettings.registerContentObserverForUser( + mSetting, mSettingsObserver, UserHandle.USER_ALL); } } diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index abbbd1713250..e38dce05a32e 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -46,6 +46,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.Assert; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.wakelock.WakeLock; import java.io.PrintWriter; @@ -163,7 +164,8 @@ public class DozeTriggers implements DozeMachine.Part { DozeParameters dozeParameters, AsyncSensorManager sensorManager, WakeLock wakeLock, DockManager dockManager, ProximitySensor proximitySensor, ProximitySensor.ProximityCheck proxCheck, - DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher) { + DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher, + SecureSettings secureSettings) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -172,7 +174,8 @@ public class DozeTriggers implements DozeMachine.Part { mWakeLock = wakeLock; mAllowPulseTriggers = true; mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters, - config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor); + config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor, + secureSettings); mUiModeManager = mContext.getSystemService(UiModeManager.class); mDockManager = dockManager; mProxCheck = proxCheck; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt index 662831e4a445..24ca9708a4e3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt @@ -27,6 +27,7 @@ import javax.inject.Inject import javax.inject.Singleton private const val TAG = "MediaDataFilter" +private const val DEBUG = true /** * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user @@ -98,7 +99,7 @@ class MediaDataFilter @Inject constructor( // are up to date mediaEntries.clear() keyCopy.forEach { - Log.d(TAG, "Removing $it after user change") + if (DEBUG) Log.d(TAG, "Removing $it after user change") listenersCopy.forEach { listener -> listener.onMediaDataRemoved(it) } @@ -106,7 +107,7 @@ class MediaDataFilter @Inject constructor( dataSource.getData().forEach { (key, data) -> if (lockscreenUserManager.isCurrentProfile(data.userId)) { - Log.d(TAG, "Re-adding $key after user change") + if (DEBUG) Log.d(TAG, "Re-adding $key after user change") mediaEntries.put(key, data) listenersCopy.forEach { listener -> listener.onMediaDataLoaded(key, null, data) @@ -119,6 +120,7 @@ class MediaDataFilter @Inject constructor( * Invoked when the user has dismissed the media carousel */ fun onSwipeToDismiss() { + if (DEBUG) Log.d(TAG, "Media carousel swiped away") val mediaKeys = mediaEntries.keys.toSet() mediaKeys.forEach { mediaDataManager.setTimedOut(it, timedOut = true) diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt index b3277737f397..d82150f2346b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt @@ -63,6 +63,7 @@ private val ART_URIS = arrayOf( ) private const val TAG = "MediaDataManager" +private const val DEBUG = true private const val DEFAULT_LUMINOSITY = 0.25f private const val LUMINOSITY_THRESHOLD = 0.05f private const val SATURATION_MULTIPLIER = 0.8f @@ -253,7 +254,7 @@ class MediaDataManager( fun removeListener(listener: Listener) = listeners.remove(listener) /** - * Called whenever the player has been paused or stopped for a while. + * Called whenever the player has been paused or stopped for a while, or swiped from QQS. * This will make the player not active anymore, hiding it from QQS and Keyguard. * @see MediaData.active */ @@ -263,6 +264,7 @@ class MediaDataManager( return } it.active = !timedOut + if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut") onMediaDataLoaded(token, token, it) } } @@ -283,7 +285,9 @@ class MediaDataManager( return } - Log.d(TAG, "adding track for $userId from browser: $desc") + if (DEBUG) { + Log.d(TAG, "adding track for $userId from browser: $desc") + } // Album art var artworkBitmap = desc.iconBitmap @@ -383,7 +387,7 @@ class MediaDataManager( if (actions != null) { for ((index, action) in actions.withIndex()) { if (action.getIcon() == null) { - Log.i(TAG, "No icon for action $index ${action.title}") + if (DEBUG) Log.i(TAG, "No icon for action $index ${action.title}") actionsToShowCollapsed.remove(index) continue } @@ -427,7 +431,7 @@ class MediaDataManager( if (!TextUtils.isEmpty(uriString)) { val albumArt = loadBitmapFromUri(Uri.parse(uriString)) if (albumArt != null) { - Log.d(TAG, "loaded art from $uri") + if (DEBUG) Log.d(TAG, "loaded art from $uri") return albumArt } } @@ -514,7 +518,7 @@ class MediaDataManager( Assert.isMainThread() val removed = mediaEntries.remove(key) if (useMediaResumption && removed?.resumeAction != null) { - Log.d(TAG, "Not removing $key because resumable") + if (DEBUG) Log.d(TAG, "Not removing $key because resumable") // Move to resume key (aka package name) if that key doesn't already exist. val resumeAction = getResumeMediaAction(removed.resumeAction!!) val updated = removed.copy(token = null, actions = listOf(resumeAction), diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java index 16e05f1e3f26..c0b9258f39fd 100644 --- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizer.java @@ -27,7 +27,6 @@ import android.graphics.Rect; import android.os.Handler; import android.os.Looper; import android.util.Log; -import android.view.Surface; import android.view.SurfaceControl; import android.window.DisplayAreaInfo; import android.window.DisplayAreaOrganizer; @@ -39,7 +38,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.systemui.Dumpable; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java index f995bf9eb2a1..71c5f8020330 100644 --- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedGestureHandler.java @@ -41,8 +41,8 @@ import androidx.annotation.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.statusbar.phone.NavigationModeController; -import com.android.systemui.wm.DisplayChangeController; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayChangeController; +import com.android.wm.shell.common.DisplayController; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java index 586761b0fc3d..70a81aaed249 100644 --- a/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/onehanded/OneHandedManagerImpl.java @@ -33,8 +33,8 @@ import com.android.systemui.R; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; -import com.android.systemui.wm.DisplayChangeController; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayChangeController; +import com.android.wm.shell.common.DisplayController; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java index 665b90e29976..df3aeadaacd6 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java @@ -39,8 +39,8 @@ import android.view.DisplayInfo; import android.view.Gravity; import android.window.WindowContainerTransaction; -import com.android.systemui.wm.DisplayController; -import com.android.systemui.wm.DisplayLayout; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index 35e56ee87967..312d6d62128f 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -58,8 +58,8 @@ import android.window.WindowOrganizer; import com.android.internal.os.SomeArgs; import com.android.systemui.pip.phone.PipUpdateThread; import com.android.systemui.stackdivider.Divider; -import com.android.systemui.wm.DisplayController; import com.android.wm.shell.R; +import com.android.wm.shell.common.DisplayController; import java.io.PrintWriter; import java.util.ArrayList; 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 02bf475d5744..582cd046f9e0 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java @@ -56,9 +56,8 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.DeviceConfigProxy; import com.android.systemui.util.FloatingContentCoordinator; -import com.android.systemui.wm.DisplayChangeController; -import com.android.systemui.wm.DisplayController; -import com.android.systemui.wm.DisplayLayout; +import com.android.wm.shell.common.DisplayChangeController; +import com.android.wm.shell.common.DisplayController; import java.io.PrintWriter; @@ -79,7 +78,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio private final DisplayInfo mTmpDisplayInfo = new DisplayInfo(); private final Rect mTmpInsetBounds = new Rect(); private final Rect mTmpNormalBounds = new Rect(); - private final Rect mReentryBounds = new Rect(); + protected final Rect mReentryBounds = new Rect(); private PipBoundsHandler mPipBoundsHandler; private InputConsumerController mInputConsumerController; diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt new file mode 100644 index 000000000000..870e714ee24c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -0,0 +1,108 @@ +/* + * 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.privacy + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import com.android.systemui.R + +class OngoingPrivacyChip @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttrs: Int = 0, + defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyleAttrs, defStyleRes) { + + private val iconMarginExpanded = context.resources.getDimensionPixelSize( + R.dimen.ongoing_appops_chip_icon_margin_expanded) + private val iconMarginCollapsed = context.resources.getDimensionPixelSize( + R.dimen.ongoing_appops_chip_icon_margin_collapsed) + private val iconSize = + context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size) + private val iconColor = context.resources.getColor( + R.color.status_bar_clock_color, context.theme) + private val sidePadding = + context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding) + private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg) + private lateinit var iconsContainer: LinearLayout + private lateinit var back: FrameLayout + var expanded = false + set(value) { + if (value != field) { + field = value + updateView(PrivacyChipBuilder(context, privacyList)) + } + } + + var privacyList = emptyList<PrivacyItem>() + set(value) { + field = value + updateView(PrivacyChipBuilder(context, field)) + } + + override fun onFinishInflate() { + super.onFinishInflate() + + back = requireViewById(R.id.background) + iconsContainer = requireViewById(R.id.icons_container) + } + + // Should only be called if the builder icons or app changed + private fun updateView(builder: PrivacyChipBuilder) { + back.background = if (expanded) backgroundDrawable else null + val padding = if (expanded) sidePadding else 0 + back.setPaddingRelative(padding, 0, padding, 0) + fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) { + iconsContainer.removeAllViews() + chipBuilder.generateIcons().forEachIndexed { i, it -> + it.mutate() + it.setTint(iconColor) + val image = ImageView(context).apply { + setImageDrawable(it) + scaleType = ImageView.ScaleType.CENTER_INSIDE + } + iconsContainer.addView(image, iconSize, iconSize) + if (i != 0) { + val lp = image.layoutParams as MarginLayoutParams + lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed + image.layoutParams = lp + } + } + } + + if (!privacyList.isEmpty()) { + generateContentDescription(builder) + setIcons(builder, iconsContainer) + val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams + lp.gravity = Gravity.CENTER_VERTICAL or + (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END) + iconsContainer.layoutParams = lp + } else { + iconsContainer.removeAllViews() + } + requestLayout() + } + + private fun generateContentDescription(builder: PrivacyChipBuilder) { + val typesText = builder.joinTypes() + contentDescription = context.getString( + R.string.ongoing_privacy_chip_content_multiple_apps, typesText) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt new file mode 100644 index 000000000000..1d2e74703b42 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt @@ -0,0 +1,51 @@ +/* + * 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.privacy + +import android.content.Context +import com.android.systemui.R + +class PrivacyChipBuilder(private val context: Context, itemsList: List<PrivacyItem>) { + + val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>> + val types: List<PrivacyType> + private val separator = context.getString(R.string.ongoing_privacy_dialog_separator) + private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator) + + init { + appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType }) + .toList() + .sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps + { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest) + types = itemsList.map { it.privacyType }.distinct().sorted() + } + + fun generateIcons() = types.map { it.getIcon(context) } + + private fun <T> List<T>.joinWithAnd(): StringBuilder { + return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply { + append(lastSeparator) + append(this@joinWithAnd.last()) + } + } + + fun joinTypes(): String { + return when (types.size) { + 0 -> "" + 1 -> types[0].getName(context) + else -> types.map { it.getName(context) }.joinWithAnd().toString() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt new file mode 100644 index 000000000000..1f24fde1377e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt @@ -0,0 +1,30 @@ +/* + * 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.privacy + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class PrivacyChipEvent(private val _id: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "Privacy chip is viewed by the user. Logged at most once per time QS is visible") + ONGOING_INDICATORS_CHIP_VIEW(601), + + @UiEvent(doc = "Privacy chip is clicked") + ONGOING_INDICATORS_CHIP_CLICK(602); + + override fun getId() = _id +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt new file mode 100644 index 000000000000..3da1363f2a56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -0,0 +1,38 @@ +/* + * 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.privacy + +import android.content.Context +import com.android.systemui.R + +typealias Privacy = PrivacyType + +enum class PrivacyType(val nameId: Int, val iconId: Int) { + // This is uses the icons used by the corresponding permission groups in the AndroidManifest + TYPE_CAMERA(R.string.privacy_type_camera, + com.android.internal.R.drawable.perm_group_camera), + TYPE_MICROPHONE(R.string.privacy_type_microphone, + com.android.internal.R.drawable.perm_group_microphone), + TYPE_LOCATION(R.string.privacy_type_location, + com.android.internal.R.drawable.perm_group_location); + + fun getName(context: Context) = context.resources.getString(nameId) + + fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme) +} + +data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) + +data class PrivacyApplication(val packageName: String, val uid: Int) diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt new file mode 100644 index 000000000000..d5a14f7bef2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -0,0 +1,296 @@ +/* + * 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.privacy + +import android.app.ActivityManager +import android.app.AppOpsManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserHandle +import android.os.UserManager +import android.provider.DeviceConfig +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.systemui.Dumpable +import com.android.systemui.appops.AppOpItem +import com.android.systemui.appops.AppOpsController +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.util.concurrency.DelayableExecutor +import java.io.FileDescriptor +import java.io.PrintWriter +import java.lang.ref.WeakReference +import java.util.concurrent.Executor +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PrivacyItemController @Inject constructor( + context: Context, + private val appOpsController: AppOpsController, + @Main uiExecutor: DelayableExecutor, + @Background private val bgExecutor: Executor, + private val broadcastDispatcher: BroadcastDispatcher, + private val deviceConfigProxy: DeviceConfigProxy, + private val userManager: UserManager, + dumpManager: DumpManager +) : Dumpable { + + @VisibleForTesting + internal companion object { + val OPS = intArrayOf(AppOpsManager.OP_CAMERA, + AppOpsManager.OP_RECORD_AUDIO, + AppOpsManager.OP_COARSE_LOCATION, + AppOpsManager.OP_FINE_LOCATION) + val intentFilter = IntentFilter().apply { + addAction(Intent.ACTION_USER_SWITCHED) + addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE) + addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE) + } + const val TAG = "PrivacyItemController" + } + + @VisibleForTesting + internal var privacyList = emptyList<PrivacyItem>() + @Synchronized get() = field.toList() // Returns a shallow copy of the list + @Synchronized set + + private fun isPermissionsHubEnabled(): Boolean { + return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false) + } + + private var currentUserIds = emptyList<Int>() + private var listening = false + private val callbacks = mutableListOf<WeakReference<Callback>>() + private val internalUiExecutor = MyExecutor(uiExecutor) + + private val notifyChanges = Runnable { + val list = privacyList + callbacks.forEach { it.get()?.onPrivacyItemsChanged(list) } + } + + private val updateListAndNotifyChanges = Runnable { + updatePrivacyList() + uiExecutor.execute(notifyChanges) + } + + var indicatorsAvailable = isPermissionsHubEnabled() + private set + @VisibleForTesting + internal val devicePropertiesChangedListener = + object : DeviceConfig.OnPropertiesChangedListener { + override fun onPropertiesChanged(properties: DeviceConfig.Properties) { + if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) && + properties.getKeyset().contains( + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED)) { + val flag = properties.getBoolean( + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false) + if (indicatorsAvailable != flag) { + // This is happening already in the UI executor, so we can iterate in the + indicatorsAvailable = flag + callbacks.forEach { it.get()?.onFlagChanged(flag) } + } + + internalUiExecutor.updateListeningState() + } + } + } + + private val cb = object : AppOpsController.Callback { + override fun onActiveStateChanged( + code: Int, + uid: Int, + packageName: String, + active: Boolean + ) { + val userId = UserHandle.getUserId(uid) + if (userId in currentUserIds) { + update(false) + } + } + } + + @VisibleForTesting + internal var userSwitcherReceiver = Receiver() + set(value) { + unregisterReceiver() + field = value + if (listening) registerReceiver() + } + + init { + deviceConfigProxy.addOnPropertiesChangedListener( + DeviceConfig.NAMESPACE_PRIVACY, + uiExecutor, + devicePropertiesChangedListener) + dumpManager.registerDumpable(TAG, this) + } + + private fun unregisterReceiver() { + broadcastDispatcher.unregisterReceiver(userSwitcherReceiver) + } + + private fun registerReceiver() { + broadcastDispatcher.registerReceiver(userSwitcherReceiver, intentFilter, + null /* handler */, UserHandle.ALL) + } + + private fun update(updateUsers: Boolean) { + bgExecutor.execute { + if (updateUsers) { + val currentUser = ActivityManager.getCurrentUser() + currentUserIds = userManager.getProfiles(currentUser).map { it.id } + } + updateListAndNotifyChanges.run() + } + } + + /** + * Updates listening status based on whether there are callbacks and the indicators are enabled + * + * This is only called from private (add/remove)Callback and from the config listener, all in + * main thread. + */ + private fun setListeningState() { + val listen = !callbacks.isEmpty() and indicatorsAvailable + if (listening == listen) return + listening = listen + if (listening) { + appOpsController.addCallback(OPS, cb) + registerReceiver() + update(true) + } else { + appOpsController.removeCallback(OPS, cb) + unregisterReceiver() + // Make sure that we remove all indicators and notify listeners if we are not + // listening anymore due to indicators being disabled + update(false) + } + } + + private fun addCallback(callback: WeakReference<Callback>) { + callbacks.add(callback) + if (callbacks.isNotEmpty() && !listening) { + internalUiExecutor.updateListeningState() + } + // Notify this callback if we didn't set to listening + else if (listening) { + internalUiExecutor.execute(NotifyChangesToCallback(callback.get(), privacyList)) + } + } + + private fun removeCallback(callback: WeakReference<Callback>) { + // Removes also if the callback is null + callbacks.removeIf { it.get()?.equals(callback.get()) ?: true } + if (callbacks.isEmpty()) { + internalUiExecutor.updateListeningState() + } + } + + fun addCallback(callback: Callback) { + addCallback(WeakReference(callback)) + } + + fun removeCallback(callback: Callback) { + removeCallback(WeakReference(callback)) + } + + private fun updatePrivacyList() { + if (!listening) { + privacyList = emptyList() + return + } + val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } + .mapNotNull { toPrivacyItem(it) }.distinct() + privacyList = list + } + + private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { + val type: PrivacyType = when (appOpItem.code) { + AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA + AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION + AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION + AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE + else -> return null + } + val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) + return PrivacyItem(type, app) + } + + interface Callback { + fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>) + @JvmDefault + fun onFlagChanged(flag: Boolean) {} + } + + internal inner class Receiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intentFilter.hasAction(intent.action)) { + update(true) + } + } + } + + private class NotifyChangesToCallback( + private val callback: Callback?, + private val list: List<PrivacyItem> + ) : Runnable { + override fun run() { + callback?.onPrivacyItemsChanged(list) + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("PrivacyItemController state:") + pw.println(" Listening: $listening") + pw.println(" Current user ids: $currentUserIds") + pw.println(" Privacy Items:") + privacyList.forEach { + pw.print(" ") + pw.println(it.toString()) + } + pw.println(" Callbacks:") + callbacks.forEach { + it.get()?.let { + pw.print(" ") + pw.println(it.toString()) + } + } + } + + private inner class MyExecutor( + private val delegate: DelayableExecutor + ) : Executor { + + private var listeningCanceller: Runnable? = null + + override fun execute(command: Runnable) { + delegate.execute(command) + } + + fun updateListeningState() { + listeningCanceller?.run() + listeningCanceller = delegate.executeDelayed({ setListeningState() }, 0L) + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index b07b1a9561ff..2dc82dd853d4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -31,6 +31,7 @@ import android.graphics.Color; import android.graphics.Rect; import android.media.AudioManager; import android.os.Handler; +import android.os.Looper; import android.provider.AlarmClock; import android.provider.Settings; import android.service.notification.ZenModeConfig; @@ -46,7 +47,9 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.Space; import android.widget.TextView; import androidx.annotation.NonNull; @@ -55,6 +58,7 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; +import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; import com.android.systemui.DualToneHandler; @@ -63,6 +67,10 @@ import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; +import com.android.systemui.privacy.OngoingPrivacyChip; +import com.android.systemui.privacy.PrivacyChipEvent; +import com.android.systemui.privacy.PrivacyItem; +import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.qs.QSDetail.Callback; import com.android.systemui.qs.carrier.QSCarrierGroup; import com.android.systemui.statusbar.CommandQueue; @@ -101,7 +109,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements private static final int TOOLTIP_NOT_YET_SHOWN_COUNT = 0; public static final int MAX_TOOLTIP_SHOWN_COUNT = 2; - private final Handler mHandler = new Handler(); private final NextAlarmController mAlarmController; private final ZenModeController mZenController; private final StatusBarIconController mStatusBarIconController; @@ -140,9 +147,14 @@ public class QuickStatusBarHeader extends RelativeLayout implements private View mRingerContainer; private Clock mClockView; private DateView mDateView; + private OngoingPrivacyChip mPrivacyChip; + private Space mSpace; private BatteryMeterView mBatteryRemainingIcon; private RingerModeTracker mRingerModeTracker; + private boolean mPermissionsHubEnabled; + private PrivacyItemController mPrivacyItemController; + private final UiEventLogger mUiEventLogger; // Used for RingerModeTracker private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @@ -156,22 +168,43 @@ public class QuickStatusBarHeader extends RelativeLayout implements private int mCutOutPaddingRight; private float mExpandedHeaderAlpha = 1.0f; private float mKeyguardExpansionFraction; + private boolean mPrivacyChipLogged = false; + + private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() { + @Override + public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) { + mPrivacyChip.setPrivacyList(privacyItems); + setChipVisibility(!privacyItems.isEmpty()); + } + + @Override + public void onFlagChanged(boolean flag) { + if (mPermissionsHubEnabled != flag) { + StatusIconContainer iconContainer = requireViewById(R.id.statusIcons); + iconContainer.setIgnoredSlots(getIgnoredIconSlots()); + setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty()); + } + } + }; @Inject public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, NextAlarmController nextAlarmController, ZenModeController zenModeController, StatusBarIconController statusBarIconController, - ActivityStarter activityStarter, - CommandQueue commandQueue, RingerModeTracker ringerModeTracker) { + ActivityStarter activityStarter, PrivacyItemController privacyItemController, + CommandQueue commandQueue, RingerModeTracker ringerModeTracker, + UiEventLogger uiEventLogger) { super(context, attrs); mAlarmController = nextAlarmController; mZenController = zenModeController; mStatusBarIconController = statusBarIconController; mActivityStarter = activityStarter; + mPrivacyItemController = privacyItemController; mDualToneHandler = new DualToneHandler( new ContextThemeWrapper(context, R.style.QSHeaderTheme)); mCommandQueue = commandQueue; mRingerModeTracker = ringerModeTracker; + mUiEventLogger = uiEventLogger; } @Override @@ -198,8 +231,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements mRingerModeTextView = findViewById(R.id.ringer_mode_text); mRingerContainer = findViewById(R.id.ringer_container); mRingerContainer.setOnClickListener(this::onClick); + mPrivacyChip = findViewById(R.id.privacy_chip); + mPrivacyChip.setOnClickListener(this::onClick); mCarrierGroup = findViewById(R.id.carrier_group); + updateResources(); Rect tintArea = new Rect(0, 0, 0, 0); @@ -219,6 +255,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements mClockView = findViewById(R.id.clock); mClockView.setOnClickListener(this); mDateView = findViewById(R.id.date); + mSpace = findViewById(R.id.space); // Tint for the battery icons are handled in setupHost() mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon); @@ -229,6 +266,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); mRingerModeTextView.setSelected(true); mNextAlarmTextView.setSelected(true); + + mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable(); } public QuickQSPanel getHeaderQsPanel() { @@ -241,6 +280,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements com.android.internal.R.string.status_bar_camera)); ignored.add(mContext.getResources().getString( com.android.internal.R.string.status_bar_microphone)); + if (mPermissionsHubEnabled) { + ignored.add(mContext.getResources().getString( + com.android.internal.R.string.status_bar_location)); + } return ignored; } @@ -256,6 +299,20 @@ public class QuickStatusBarHeader extends RelativeLayout implements } } + private void setChipVisibility(boolean chipVisible) { + if (chipVisible && mPermissionsHubEnabled) { + mPrivacyChip.setVisibility(View.VISIBLE); + // Makes sure that the chip is logged as viewed at most once each time QS is opened + // mListening makes sure that the callback didn't return after the user closed QS + if (!mPrivacyChipLogged && mListening) { + mPrivacyChipLogged = true; + mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW); + } + } else { + mPrivacyChip.setVisibility(View.GONE); + } + } + private boolean updateRingerStatus() { boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE; CharSequence originalRingerText = mRingerModeTextView.getText(); @@ -363,6 +420,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements updateStatusIconAlphaAnimator(); updateHeaderTextContainerAlphaAnimator(); + updatePrivacyChipAlphaAnimator(); } private void updateStatusIconAlphaAnimator() { @@ -377,6 +435,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements .build(); } + private void updatePrivacyChipAlphaAnimator() { + mPrivacyChipAlphaAnimator = new TouchAnimator.Builder() + .addFloat(mPrivacyChip, "alpha", 1, 0, 1) + .build(); + } + public void setExpanded(boolean expanded) { if (mExpanded == expanded) return; mExpanded = expanded; @@ -415,6 +479,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements mHeaderTextContainerView.setVisibility(INVISIBLE); } } + if (mPrivacyChipAlphaAnimator != null) { + mPrivacyChip.setExpanded(expansionFraction > 0.5); + mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction); + } if (expansionFraction < 1 && expansionFraction > 0.99) { if (mHeaderQsPanel.switchTileLayout()) { updateResources(); @@ -453,6 +521,31 @@ public class QuickStatusBarHeader extends RelativeLayout implements Pair<Integer, Integer> padding = StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner( cutout, cornerCutoutPadding, -1); + if (padding == null) { + mSystemIconsView.setPaddingRelative( + getResources().getDimensionPixelSize(R.dimen.status_bar_padding_start), 0, + getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end), 0); + } else { + mSystemIconsView.setPadding(padding.first, 0, padding.second, 0); + + } + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams(); + boolean cornerCutout = cornerCutoutPadding != null + && (cornerCutoutPadding.first == 0 || cornerCutoutPadding.second == 0); + if (cutout != null) { + Rect topCutout = cutout.getBoundingRectTop(); + if (topCutout.isEmpty() || cornerCutout) { + mHasTopCutout = false; + lp.width = 0; + mSpace.setVisibility(View.GONE); + } else { + mHasTopCutout = true; + lp.width = topCutout.width(); + mSpace.setVisibility(View.VISIBLE); + } + } + mSpace.setLayoutParams(lp); + setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE); mCutOutPaddingLeft = padding.first; mCutOutPaddingRight = padding.second; mWaterfallTopInset = cutout == null ? 0 : cutout.getWaterfallInsets().top; @@ -513,10 +606,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements mZenController.addCallback(this); mAlarmController.addCallback(this); mLifecycle.setCurrentState(Lifecycle.State.RESUMED); + // Get the most up to date info + mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable(); + mPrivacyItemController.addCallback(mPICCallback); } else { mZenController.removeCallback(this); mAlarmController.removeCallback(this); mLifecycle.setCurrentState(Lifecycle.State.CREATED); + mPrivacyItemController.removeCallback(mPICCallback); + mPrivacyChipLogged = false; } } @@ -534,6 +632,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements mActivityStarter.postStartActivityDismissingKeyguard(new Intent( AlarmClock.ACTION_SHOW_ALARMS), 0); } + } else if (v == mPrivacyChip) { + // If the privacy chip is visible, it means there were some indicators + Handler mUiHandler = new Handler(Looper.getMainLooper()); + mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_CLICK); + mUiHandler.post(() -> { + mActivityStarter.postStartActivityDismissingKeyguard( + new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0); + mHost.collapsePanels(); + }); } else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) { mActivityStarter.postStartActivityDismissingKeyguard(new Intent( Settings.ACTION_SOUND_SETTINGS), 0); diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 9115b4849355..d03082e6b442 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -809,7 +809,9 @@ public class OverviewProxyService extends CurrentUserTracker implements @Override public void addCallback(OverviewProxyListener listener) { - mConnectionCallbacks.add(listener); + if (!mConnectionCallbacks.contains(listener)) { + mConnectionCallbacks.add(listener); + } listener.onConnectionChanged(mOverviewProxy != null); listener.onNavBarButtonAlphaChanged(mNavBarButtonAlpha, false); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java new file mode 100644 index 000000000000..3fd7f94514f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java @@ -0,0 +1,105 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_EDIT; +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.statusbar.phone.StatusBar; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.inject.Inject; + +/** + * Receiver to proxy the share or edit intent, used to clean up the notification and send + * appropriate signals to the system (ie. to dismiss the keyguard if necessary). + */ +public class ActionProxyReceiver extends BroadcastReceiver { + private static final String TAG = "ActionProxyReceiver"; + + private static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000; + private final StatusBar mStatusBar; + private final ActivityManagerWrapper mActivityManagerWrapper; + private final ScreenshotSmartActions mScreenshotSmartActions; + + @Inject + public ActionProxyReceiver(Optional<StatusBar> statusBar, + ActivityManagerWrapper activityManagerWrapper, + ScreenshotSmartActions screenshotSmartActions) { + mStatusBar = statusBar.orElse(null); + mActivityManagerWrapper = activityManagerWrapper; + mScreenshotSmartActions = screenshotSmartActions; + } + + @Override + public void onReceive(Context context, final Intent intent) { + Runnable startActivityRunnable = () -> { + try { + mActivityManagerWrapper.closeSystemWindows( + SYSTEM_DIALOG_REASON_SCREENSHOT).get( + CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + } catch (TimeoutException | InterruptedException | ExecutionException e) { + Log.e(TAG, "Unable to share screenshot", e); + return; + } + + PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + ActivityOptions opts = ActivityOptions.makeBasic(); + opts.setDisallowEnterPictureInPictureWhileLaunching( + intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); + try { + actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled", e); + } + + }; + + if (mStatusBar != null) { + mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null, + true /* dismissShade */, true /* afterKeyguardGone */, + true /* deferred */); + } else { + startActivityRunnable.run(); + } + + if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { + String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) + ? ACTION_TYPE_EDIT + : ACTION_TYPE_SHARE; + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), actionType, false); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java deleted file mode 100644 index 8c4865510ed1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteImageInBackgroundTask.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot; - -import android.content.ContentResolver; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; - -/** - * An AsyncTask that deletes an image from the media store in the background. - */ -class DeleteImageInBackgroundTask extends AsyncTask<Uri, Void, Void> { - private Context mContext; - - DeleteImageInBackgroundTask(Context context) { - mContext = context; - } - - @Override - protected Void doInBackground(Uri... params) { - if (params.length != 1) return null; - - Uri screenshotUri = params[0]; - ContentResolver resolver = mContext.getContentResolver(); - resolver.delete(screenshotUri, null, null); - return null; - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java new file mode 100644 index 000000000000..9028bb57c8e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java @@ -0,0 +1,68 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import com.android.systemui.dagger.qualifiers.Background; + +import java.util.concurrent.Executor; + +import javax.inject.Inject; + +/** + * Removes the file at a provided URI. + */ +public class DeleteScreenshotReceiver extends BroadcastReceiver { + + private final ScreenshotSmartActions mScreenshotSmartActions; + private final Executor mBackgroundExecutor; + + @Inject + public DeleteScreenshotReceiver(ScreenshotSmartActions screenshotSmartActions, + @Background Executor backgroundExecutor) { + mScreenshotSmartActions = screenshotSmartActions; + mBackgroundExecutor = backgroundExecutor; + } + + @Override + public void onReceive(Context context, Intent intent) { + if (!intent.hasExtra(SCREENSHOT_URI_ID)) { + return; + } + + // And delete the image from the media store + final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); + mBackgroundExecutor.execute(() -> { + ContentResolver resolver = context.getContentResolver(); + resolver.delete(uri, null, null); + }); + if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 4a4aa79aadfa..c53523032353 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -21,8 +21,6 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -30,13 +28,10 @@ import android.animation.ValueAnimator; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.app.Notification; import android.app.PendingIntent; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -62,7 +57,6 @@ import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; import android.util.MathUtils; -import android.util.Slog; import android.view.Display; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -87,23 +81,15 @@ import android.widget.Toast; import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import javax.inject.Inject; import javax.inject.Singleton; -import dagger.Lazy; - /** * Class for handling device screen shots */ @@ -192,6 +178,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final UiEventLogger mUiEventLogger; private final Context mContext; + private final ScreenshotSmartActions mScreenshotSmartActions; private final WindowManager mWindowManager; private final WindowManager.LayoutParams mWindowLayoutParams; private final Display mDisplay; @@ -247,9 +234,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Inject public GlobalScreenshot( Context context, @Main Resources resources, + ScreenshotSmartActions screenshotSmartActions, ScreenshotNotificationsController screenshotNotificationsController, UiEventLogger uiEventLogger) { mContext = context; + mScreenshotSmartActions = screenshotSmartActions; mNotificationsController = screenshotNotificationsController; mUiEventLogger = uiEventLogger; @@ -504,7 +493,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> { if (keyCode == KeyEvent.KEYCODE_BACK) { - dismissScreenshot("back pressed", true); + dismissScreenshot("back pressed", false); return true; } return false; @@ -704,7 +693,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset }); } - mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data); + mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); mSaveInBgTask.execute(); } @@ -1024,6 +1013,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); + mScreenshotPreview.setOnClickListener(null); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); @@ -1116,119 +1106,4 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return insetDrawable; } } - - /** - * Receiver to proxy the share or edit intent, used to clean up the notification and send - * appropriate signals to the system (ie. to dismiss the keyguard if necessary). - */ - public static class ActionProxyReceiver extends BroadcastReceiver { - static final int CLOSE_WINDOWS_TIMEOUT_MILLIS = 3000; - private final StatusBar mStatusBar; - - @Inject - public ActionProxyReceiver(Optional<Lazy<StatusBar>> statusBarLazy) { - Lazy<StatusBar> statusBar = statusBarLazy.orElse(null); - mStatusBar = statusBar != null ? statusBar.get() : null; - } - - @Override - public void onReceive(Context context, final Intent intent) { - Runnable startActivityRunnable = () -> { - try { - ActivityManagerWrapper.getInstance().closeSystemWindows( - SYSTEM_DIALOG_REASON_SCREENSHOT).get( - CLOSE_WINDOWS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); - } catch (TimeoutException | InterruptedException | ExecutionException e) { - Slog.e(TAG, "Unable to share screenshot", e); - return; - } - - PendingIntent actionIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); - if (intent.getBooleanExtra(EXTRA_CANCEL_NOTIFICATION, false)) { - ScreenshotNotificationsController.cancelScreenshotNotification(context); - } - ActivityOptions opts = ActivityOptions.makeBasic(); - opts.setDisallowEnterPictureInPictureWhileLaunching( - intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false)); - try { - actionIntent.send(context, 0, null, null, null, null, opts.toBundle()); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Pending intent canceled", e); - } - - }; - - if (mStatusBar != null) { - mStatusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null, - true /* dismissShade */, true /* afterKeyguardGone */, - true /* deferred */); - } else { - startActivityRunnable.run(); - } - - if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { - String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) - ? ACTION_TYPE_EDIT - : ACTION_TYPE_SHARE; - ScreenshotSmartActions.notifyScreenshotAction( - context, intent.getStringExtra(EXTRA_ID), actionType, false); - } - } - } - - /** - * Removes the notification for a screenshot after a share target is chosen. - */ - public static class TargetChosenReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - // Clear the notification only after the user has chosen a share action - ScreenshotNotificationsController.cancelScreenshotNotification(context); - } - } - - /** - * Removes the last screenshot. - */ - public static class DeleteScreenshotReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (!intent.hasExtra(SCREENSHOT_URI_ID)) { - return; - } - - // Clear the notification when the image is deleted - ScreenshotNotificationsController.cancelScreenshotNotification(context); - - // And delete the image from the media store - final Uri uri = Uri.parse(intent.getStringExtra(SCREENSHOT_URI_ID)); - new DeleteImageInBackgroundTask(context).execute(uri); - if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { - ScreenshotSmartActions.notifyScreenshotAction( - context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false); - } - } - } - - /** - * Executes the smart action tapped by the user in the notification. - */ - public static class SmartActionsReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); - String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); - Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); - ActivityOptions opts = ActivityOptions.makeBasic(); - - try { - pendingIntent.send(context, 0, null, null, null, null, opts.toBundle()); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Pending intent canceled", e); - } - - ScreenshotSmartActions.notifyScreenshotAction( - context, intent.getStringExtra(EXTRA_ID), actionType, true); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index 468b9b16addb..df1d78953f46 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -81,6 +81,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private static final String SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)"; private final Context mContext; + private final ScreenshotSmartActions mScreenshotSmartActions; private final GlobalScreenshot.SaveImageInBackgroundData mParams; private final GlobalScreenshot.SavedImageData mImageData; private final String mImageFileName; @@ -90,8 +91,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final boolean mSmartActionsEnabled; private final Random mRandom = new Random(); - SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { + SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions, + GlobalScreenshot.SaveImageInBackgroundData data) { mContext = context; + mScreenshotSmartActions = screenshotSmartActions; mImageData = new GlobalScreenshot.SavedImageData(); // Prepare all the output metadata @@ -141,7 +144,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); CompletableFuture<List<Notification.Action>> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( mScreenshotId, uri, image, mSmartActionsProvider, mSmartActionsEnabled, getUserHandle(mContext)); @@ -199,7 +202,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { SystemUiDeviceConfigFlags.SCREENSHOT_NOTIFICATION_SMART_ACTIONS_TIMEOUT_MS, 1000); smartActions.addAll(buildSmartActions( - ScreenshotSmartActions.getSmartActions( + mScreenshotSmartActions.getSmartActions( mScreenshotId, smartActionsFuture, timeoutMs, mSmartActionsProvider), mContext)); @@ -274,11 +277,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); - PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent sharingChooserIntent = - Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender()) + Intent.createChooser(sharingIntent, null) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -288,7 +288,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) + new Intent(context, ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) @@ -333,10 +333,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, - new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) + new Intent(context, ActionProxyReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent) - .putExtra(GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION, - editIntent.getComponent() != null) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, mSmartActionsEnabled) @@ -358,7 +356,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Create a delete action for the notification PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) + new Intent(context, DeleteScreenshotReceiver.class) .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, @@ -398,7 +396,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { String actionType = extras.getString( ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE); - Intent intent = new Intent(context, GlobalScreenshot.SmartActionsReceiver.class) + Intent intent = new Intent(context, SmartActionsReceiver.class) .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java index 442b373b31be..633cdd6ca5ca 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java @@ -39,14 +39,21 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import javax.inject.Inject; +import javax.inject.Singleton; + /** * Collects the static functions for retrieving and acting on smart actions. */ +@Singleton public class ScreenshotSmartActions { private static final String TAG = "ScreenshotSmartActions"; + @Inject + public ScreenshotSmartActions() {} + @VisibleForTesting - static CompletableFuture<List<Notification.Action>> getSmartActionsFuture( + CompletableFuture<List<Notification.Action>> getSmartActionsFuture( String screenshotId, Uri screenshotUri, Bitmap image, ScreenshotNotificationSmartActionsProvider smartActionsProvider, boolean smartActionsEnabled, UserHandle userHandle) { @@ -86,7 +93,7 @@ public class ScreenshotSmartActions { } @VisibleForTesting - static List<Notification.Action> getSmartActions(String screenshotId, + List<Notification.Action> getSmartActions(String screenshotId, CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs, ScreenshotNotificationSmartActionsProvider smartActionsProvider) { long startTimeMs = SystemClock.uptimeMillis(); @@ -116,7 +123,7 @@ public class ScreenshotSmartActions { } } - static void notifyScreenshotOp(String screenshotId, + void notifyScreenshotOp(String screenshotId, ScreenshotNotificationSmartActionsProvider smartActionsProvider, ScreenshotNotificationSmartActionsProvider.ScreenshotOp op, ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus status, long durationMs) { @@ -127,7 +134,7 @@ public class ScreenshotSmartActions { } } - static void notifyScreenshotAction(Context context, String screenshotId, String action, + void notifyScreenshotAction(Context context, String screenshotId, String action, boolean isSmartAction) { try { ScreenshotNotificationSmartActionsProvider provider = diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java new file mode 100644 index 000000000000..217235b16ecf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java @@ -0,0 +1,63 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.util.Slog; + +import javax.inject.Inject; + + +/** + * Executes the smart action tapped by the user in the notification. + */ +public class SmartActionsReceiver extends BroadcastReceiver { + private static final String TAG = "SmartActionsReceiver"; + + private final ScreenshotSmartActions mScreenshotSmartActions; + + @Inject + SmartActionsReceiver(ScreenshotSmartActions screenshotSmartActions) { + mScreenshotSmartActions = screenshotSmartActions; + } + + @Override + public void onReceive(Context context, Intent intent) { + PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT); + String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE); + Slog.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent()); + ActivityOptions opts = ActivityOptions.makeBasic(); + + try { + pendingIntent.send(context, 0, null, null, null, null, opts.toBundle()); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Pending intent canceled", e); + } + + mScreenshotSmartActions.notifyScreenshotAction( + context, intent.getStringExtra(EXTRA_ID), actionType, true); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 6f143da5405a..a043f0f1e50c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -61,7 +61,7 @@ public class TakeScreenshotService extends Service { @Override public void onReceive(Context context, Intent intent) { if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) { - mScreenshot.dismissScreenshot("close system dialogs", true); + mScreenshot.dismissScreenshot("close system dialogs", false); } } }; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java index 570a4bb3cd12..eb7231211ea8 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java @@ -39,16 +39,16 @@ import android.window.WindowOrganizer; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.systemui.R; import com.android.systemui.SystemUI; -import com.android.systemui.TransactionPool; import com.android.systemui.recents.Recents; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.wm.DisplayChangeController; -import com.android.systemui.wm.DisplayController; -import com.android.systemui.wm.DisplayImeController; -import com.android.systemui.wm.DisplayLayout; -import com.android.systemui.wm.SystemWindows; +import com.android.wm.shell.common.DisplayChangeController; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.DisplayLayout; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; import java.io.FileDescriptor; import java.io.PrintWriter; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java index 5aeca5e07bdd..84ec38744e98 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerImeController.java @@ -33,8 +33,8 @@ import android.window.WindowOrganizer; import androidx.annotation.Nullable; -import com.android.systemui.TransactionPool; -import com.android.systemui.wm.DisplayImeController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.TransactionPool; class DividerImeController implements DisplayImeController.ImePositionProcessor { private static final String TAG = "DividerImeController"; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java index 3b7f3152ec76..c24431c22d62 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerModule.java @@ -19,13 +19,13 @@ package com.android.systemui.stackdivider; import android.content.Context; import android.os.Handler; -import com.android.systemui.TransactionPool; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.recents.Recents; import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.wm.DisplayController; -import com.android.systemui.wm.DisplayImeController; -import com.android.systemui.wm.SystemWindows; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; import java.util.Optional; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java index 6ea3132ac942..d869333e11a7 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerWindowManager.java @@ -32,7 +32,7 @@ import android.os.Binder; import android.view.View; import android.view.WindowManager; -import com.android.systemui.wm.SystemWindows; +import com.android.wm.shell.common.SystemWindows; /** * Manages the window parameters of the docked stack divider. diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java index 69095f7538c5..a34e85517953 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitDisplayLayout.java @@ -34,7 +34,7 @@ import android.window.WindowContainerTransaction; import com.android.internal.policy.DividerSnapAlgorithm; import com.android.internal.policy.DockedDividerUtils; -import com.android.systemui.wm.DisplayLayout; +import com.android.wm.shell.common.DisplayLayout; /** * Handles split-screen related internal display layout. In general, this represents the diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java index 1ff404677ea6..6812f62422a7 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SyncTransactionQueue.java @@ -25,7 +25,7 @@ import android.window.WindowOrganizer; import androidx.annotation.NonNull; -import com.android.systemui.TransactionPool; +import com.android.wm.shell.common.TransactionPool; import java.util.ArrayList; diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java index 410e3dd39a0b..2b3681281064 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java @@ -40,7 +40,7 @@ import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; import com.android.internal.annotations.GuardedBy; -import com.android.systemui.TransactionPool; +import com.android.wm.shell.common.TransactionPool; import java.util.ArrayList; import java.util.List; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 8c24c540e743..2638d28733e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -154,7 +154,7 @@ public class NavigationBarController implements Callbacks { Dependency.get(IWindowManager.class)); navBar.setAutoHideController(autoHideController); navBar.restoreAppearanceAndTransientState(); - mNavigationBars.append(displayId, navBar); + mNavigationBars.put(displayId, navBar); if (result != null) { navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken, 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 9abc66056452..aba9e1005559 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -24,18 +24,24 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; +import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationListener; @@ -52,6 +58,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.logging.NotificationLogger; import com.android.systemui.statusbar.phone.NotificationGroupManager; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Assert; import com.android.systemui.util.leak.LeakDetector; @@ -127,6 +134,8 @@ public class NotificationEntryManager implements private final NotificationEntryManagerLogger mLogger; + private final IStatusBarService mStatusBarService; + // Lazily retrieved dependencies private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy; private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy; @@ -138,6 +147,8 @@ public class NotificationEntryManager implements private final NotificationRankingManager mRankingManager; private final FeatureFlags mFeatureFlags; private final ForegroundServiceDismissalFeatureController mFgsFeatureController; + private final HeadsUpManager mHeadsUpManager; + private final StatusBarStateController mStatusBarStateController; private NotificationPresenter mPresenter; private RankingMap mLatestRankingMap; @@ -201,7 +212,10 @@ public class NotificationEntryManager implements Lazy<NotificationRowBinder> notificationRowBinderLazy, Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, LeakDetector leakDetector, - ForegroundServiceDismissalFeatureController fgsFeatureController) { + ForegroundServiceDismissalFeatureController fgsFeatureController, + HeadsUpManager headsUpManager, + StatusBarStateController statusBarStateController + ) { mLogger = logger; mGroupManager = groupManager; mRankingManager = rankingManager; @@ -211,6 +225,11 @@ public class NotificationEntryManager implements mRemoteInputManagerLazy = notificationRemoteInputManagerLazy; mLeakDetector = leakDetector; mFgsFeatureController = fgsFeatureController; + mHeadsUpManager = headsUpManager; + mStatusBarStateController = statusBarStateController; + + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.checkService(Context.STATUS_BAR_SERVICE)); } /** Once called, the NEM will start processing notification events from system server. */ @@ -496,6 +515,9 @@ public class NotificationEntryManager implements removedByUser |= entryDismissed; mLogger.logNotifRemoved(entry.getKey(), removedByUser); + if (removedByUser && visibility != null) { + sendNotificationRemovalToServer(entry.getKey(), entry.getSbn(), visibility); + } for (NotificationEntryListener listener : mNotificationEntryListeners) { listener.onEntryRemoved(entry, visibility, removedByUser, reason); } @@ -511,6 +533,36 @@ public class NotificationEntryManager implements } } + private void sendNotificationRemovalToServer( + String key, + StatusBarNotification notification, + NotificationVisibility nv) { + final String pkg = notification.getPackageName(); + final String tag = notification.getTag(); + final int id = notification.getId(); + final int userId = notification.getUser().getIdentifier(); + try { + int dismissalSurface = NotificationStats.DISMISSAL_SHADE; + if (mHeadsUpManager.isAlerting(key)) { + dismissalSurface = NotificationStats.DISMISSAL_PEEK; + } else if (mStatusBarStateController.isDozing()) { + dismissalSurface = NotificationStats.DISMISSAL_AOD; + } + int dismissalSentiment = NotificationStats.DISMISS_SENTIMENT_NEUTRAL; + mStatusBarService.onNotificationClear( + pkg, + tag, + id, + userId, + notification.getKey(), + dismissalSurface, + dismissalSentiment, + nv); + } catch (RemoteException ex) { + // system process is dead if we're here. + } + } + /** * Ensures that the group children are cancelled immediately when the group summary is cancelled * instead of waiting for the notification manager to send all cancels. Otherwise this could diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt index 27476964b9af..f982cf05de97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt @@ -382,7 +382,7 @@ class NotificationWakeUpCoordinator @Inject constructor( } private fun shouldAnimateVisibility() = - dozeParameters.getAlwaysOn() && !dozeParameters.getDisplayNeedsBlanking() + dozeParameters.alwaysOn && !dozeParameters.displayNeedsBlanking interface WakeUpListener { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index d2c202cb485f..d661b5e2b7cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -59,6 +59,7 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.leak.LeakDetector; import java.util.concurrent.Executor; @@ -88,7 +89,9 @@ public interface NotificationsModule { Lazy<NotificationRowBinder> notificationRowBinderLazy, Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy, LeakDetector leakDetector, - ForegroundServiceDismissalFeatureController fgsFeatureController) { + ForegroundServiceDismissalFeatureController fgsFeatureController, + HeadsUpManager headsUpManager, + StatusBarStateController statusBarStateController) { return new NotificationEntryManager( logger, groupManager, @@ -98,7 +101,9 @@ public interface NotificationsModule { notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector, - fgsFeatureController); + fgsFeatureController, + headsUpManager, + statusBarStateController); } /** Provides an instance of {@link NotificationGutsManager} */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java index bd0d0b31e4dc..4441270f895b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java @@ -21,7 +21,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.service.notification.NotificationListenerService; -import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.ArraySet; @@ -44,7 +43,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.dagger.NotificationsModule; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.policy.HeadsUpManager; import java.util.Collection; import java.util.Collections; @@ -74,7 +72,6 @@ public class NotificationLogger implements StateListener { private final Executor mUiBgExecutor; private final NotificationEntryManager mEntryManager; private final NotificationPanelLogger mNotificationPanelLogger; - private HeadsUpManager mHeadsUpManager; private final ExpansionStateLogger mExpansionStateLogger; protected Handler mHandler = new Handler(); @@ -226,9 +223,6 @@ public class NotificationLogger implements StateListener { NotificationVisibility visibility, boolean removedByUser, int reason) { - if (removedByUser && visibility != null) { - logNotificationClear(entry.getKey(), entry.getSbn(), visibility); - } mExpansionStateLogger.onEntryRemoved(entry.getKey()); } @@ -250,10 +244,6 @@ public class NotificationLogger implements StateListener { mListContainer = listContainer; } - public void setHeadsUpManager(HeadsUpManager headsUpManager) { - mHeadsUpManager = headsUpManager; - } - public void stopNotificationLogging() { if (mLogging) { mLogging = false; @@ -296,30 +286,6 @@ public class NotificationLogger implements StateListener { } } - // TODO: This method has side effects, it is NOT just logging that a notification - // was cleared, it also actually removes the notification - private void logNotificationClear(String key, StatusBarNotification notification, - NotificationVisibility nv) { - final String pkg = notification.getPackageName(); - final String tag = notification.getTag(); - final int id = notification.getId(); - final int userId = notification.getUserId(); - try { - int dismissalSurface = NotificationStats.DISMISSAL_SHADE; - if (mHeadsUpManager.isAlerting(key)) { - dismissalSurface = NotificationStats.DISMISSAL_PEEK; - } else if (mListContainer.hasPulsingNotifications()) { - dismissalSurface = NotificationStats.DISMISSAL_AOD; - } - int dismissalSentiment = NotificationStats.DISMISS_SENTIMENT_NEUTRAL; - mBarService.onNotificationClear(pkg, tag, id, userId, notification.getKey(), - dismissalSurface, - dismissalSentiment, nv); - } catch (RemoteException ex) { - // system process is dead if we're here. - } - } - /** * Logs Notification inflation error */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index f5ea1c880a41..5fab4bea9a04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -28,6 +28,7 @@ import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; +import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; import java.io.PrintWriter; @@ -52,6 +53,7 @@ public class DozeParameters implements TunerService.Tunable, private final AlwaysOnDisplayPolicy mAlwaysOnPolicy; private final Resources mResources; + private final BatteryController mBatteryController; private boolean mDozeAlwaysOn; private boolean mControlScreenOffAnimation; @@ -62,10 +64,12 @@ public class DozeParameters implements TunerService.Tunable, AmbientDisplayConfiguration ambientDisplayConfiguration, AlwaysOnDisplayPolicy alwaysOnDisplayPolicy, PowerManager powerManager, + BatteryController batteryController, TunerService tunerService) { mResources = resources; mAmbientDisplayConfiguration = ambientDisplayConfiguration; mAlwaysOnPolicy = alwaysOnDisplayPolicy; + mBatteryController = batteryController; mControlScreenOffAnimation = !getDisplayNeedsBlanking(); mPowerManager = powerManager; @@ -164,7 +168,7 @@ public class DozeParameters implements TunerService.Tunable, * @return {@code true} if enabled and available. */ public boolean getAlwaysOn() { - return mDozeAlwaysOn; + return mDozeAlwaysOn && !mBatteryController.isAodPowerSave(); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index a35aca553c4f..9606318e1992 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -121,6 +121,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private final Context mContext; private final OverviewProxyService mOverviewProxyService; + private final SysUiState mSysUiState; private final Runnable mStateChangeCallback; private final PluginManager mPluginManager; @@ -197,14 +198,22 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } }; + private final SysUiState.SysUiStateCallback mSysUiStateCallback = + new SysUiState.SysUiStateCallback() { + @Override + public void onSystemUiStateChanged(int sysUiFlags) { + mSysUiFlags = sysUiFlags; + } + }; + public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, - SysUiState sysUiFlagContainer, PluginManager pluginManager, - Runnable stateChangeCallback) { + SysUiState sysUiState, PluginManager pluginManager, Runnable stateChangeCallback) { super(Dependency.get(BroadcastDispatcher.class)); mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; + mSysUiState = sysUiState; mPluginManager = pluginManager; mStateChangeCallback = stateChangeCallback; ComponentName recentsComponentName = ComponentName.unflattenFromString( @@ -238,7 +247,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); updateCurrentUserResources(); - sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); } public void updateCurrentUserResources() { @@ -287,6 +295,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mIsAttached = true; Dependency.get(ProtoTracer.class).add(this); mOverviewProxyService.addCallback(mQuickSwitchListener); + mSysUiState.addCallback(mSysUiStateCallback); updateIsEnabled(); startTracking(); } @@ -298,6 +307,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mIsAttached = false; Dependency.get(ProtoTracer.class).remove(this); mOverviewProxyService.removeCallback(mQuickSwitchListener); + mSysUiState.removeCallback(mSysUiStateCallback); updateIsEnabled(); stopTracking(); } 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 b7733cc5acd7..f43fa648a1a2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java @@ -120,6 +120,7 @@ import com.android.systemui.stackdivider.Divider; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CommandQueue.Callbacks; +import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; @@ -131,6 +132,7 @@ import com.android.systemui.util.LifecycleFragment; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.List; import java.util.Locale; import java.util.Optional; @@ -354,6 +356,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback // If the button will actually become visible and the navbar is about to hide, // tell the statusbar to keep it around for longer mAutoHideController.touchAutoHide(); + mNavigationBarView.notifyActiveTouchRegions(); } }; @@ -550,6 +553,9 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } + mHandler.removeCallbacks(mAutoDim); + mNavigationBarView = null; + mOrientationHandle = null; } @Override @@ -1458,11 +1464,11 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView); if (navigationBarView == null) return null; - final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView) - .create(NavigationBarFragment.class); navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { + final NavigationBarFragment fragment = + FragmentHostManager.get(v).create(NavigationBarFragment.class); final FragmentHostManager fragmentHost = FragmentHostManager.get(v); fragmentHost.getFragmentManager().beginTransaction() .replace(R.id.navigation_bar_frame, fragment, TAG) @@ -1472,6 +1478,8 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback @Override public void onViewDetachedFromWindow(View v) { + final FragmentHostManager fragmentHost = FragmentHostManager.get(v); + fragmentHost.removeTagListener(TAG, listener); FragmentHostManager.removeAndDestroy(v); navigationBarView.removeOnAttachStateChangeListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 7936e533f76d..84512ac85fa9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -115,12 +115,8 @@ public class NavigationBarView extends FrameLayout implements int mNavigationIconHints = 0; private int mNavBarMode; - private Rect mHomeButtonBounds = new Rect(); - private Rect mBackButtonBounds = new Rect(); - private Rect mRecentsButtonBounds = new Rect(); - private Rect mRotationButtonBounds = new Rect(); private final Region mActiveRegion = new Region(); - private int[] mTmpPosition = new int[2]; + private Rect mTmpBounds = new Rect(); private KeyButtonDrawable mBackIcon; private KeyButtonDrawable mHomeDefaultIcon; @@ -712,6 +708,7 @@ public class NavigationBarView extends FrameLayout implements getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE); + notifyActiveTouchRegions(); } @VisibleForTesting @@ -929,42 +926,30 @@ public class NavigationBarView extends FrameLayout implements protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); + notifyActiveTouchRegions(); + mRecentsOnboarding.setNavBarHeight(getMeasuredHeight()); + } + + /** + * Notifies the overview service of the active touch regions. + */ + public void notifyActiveTouchRegions() { mActiveRegion.setEmpty(); - updateButtonLocation(getBackButton(), mBackButtonBounds, true); - updateButtonLocation(getHomeButton(), mHomeButtonBounds, false); - updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false); - updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true); - // TODO: Handle button visibility changes + updateButtonLocation(getBackButton()); + updateButtonLocation(getHomeButton()); + updateButtonLocation(getRecentsButton()); + updateButtonLocation(getRotateSuggestionButton()); mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion); - mRecentsOnboarding.setNavBarHeight(getMeasuredHeight()); } - private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds, - boolean isActive) { + private void updateButtonLocation(ButtonDispatcher button) { View view = button.getCurrentView(); - if (view == null) { - buttonBounds.setEmpty(); + if (view == null || !button.isVisible()) { return; } - // Temporarily reset the translation back to origin to get the position in window - final float posX = view.getTranslationX(); - final float posY = view.getTranslationY(); - view.setTranslationX(0); - view.setTranslationY(0); - - if (isActive) { - view.getLocationOnScreen(mTmpPosition); - buttonBounds.set(mTmpPosition[0], mTmpPosition[1], - mTmpPosition[0] + view.getMeasuredWidth(), - mTmpPosition[1] + view.getMeasuredHeight()); - mActiveRegion.op(buttonBounds, Op.UNION); - } - view.getLocationInWindow(mTmpPosition); - buttonBounds.set(mTmpPosition[0], mTmpPosition[1], - mTmpPosition[0] + view.getMeasuredWidth(), - mTmpPosition[1] + view.getMeasuredHeight()); - view.setTranslationX(posX); - view.setTranslationY(posY); + + view.getBoundsOnScreen(mTmpBounds); + mActiveRegion.op(mTmpBounds, Op.UNION); } private void updateOrientationViews() { 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 5bb8fab8a62e..b2cfceae2cf6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -47,6 +47,9 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; +import com.android.systemui.privacy.PrivacyItem; +import com.android.systemui.privacy.PrivacyItemController; +import com.android.systemui.privacy.PrivacyType; import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.screenrecord.RecordingController; @@ -70,6 +73,9 @@ import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.time.DateFormatUtil; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.List; import java.util.Locale; import java.util.concurrent.Executor; @@ -87,13 +93,13 @@ public class PhoneStatusBarPolicy ZenModeController.Callback, DeviceProvisionedListener, KeyguardStateController.Callback, + PrivacyItemController.Callback, LocationController.LocationChangeCallback, RecordingController.RecordingStateChangeCallback { private static final String TAG = "PhoneStatusBarPolicy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - static final int LOCATION_STATUS_ICON_ID = - com.android.internal.R.drawable.perm_group_location; + static final int LOCATION_STATUS_ICON_ID = PrivacyType.TYPE_LOCATION.getIconId(); private final String mSlotCast; private final String mSlotHotspot; @@ -107,6 +113,8 @@ public class PhoneStatusBarPolicy private final String mSlotHeadset; private final String mSlotDataSaver; private final String mSlotLocation; + private final String mSlotMicrophone; + private final String mSlotCamera; private final String mSlotSensorsOff; private final String mSlotScreenRecord; private final int mDisplayId; @@ -132,6 +140,7 @@ public class PhoneStatusBarPolicy private final DeviceProvisionedController mProvisionedController; private final KeyguardStateController mKeyguardStateController; private final LocationController mLocationController; + private final PrivacyItemController mPrivacyItemController; private final Executor mUiBgExecutor; private final SensorPrivacyController mSensorPrivacyController; private final RecordingController mRecordingController; @@ -162,7 +171,8 @@ public class PhoneStatusBarPolicy RecordingController recordingController, @Nullable TelecomManager telecomManager, @DisplayId int displayId, @Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil, - RingerModeTracker ringerModeTracker) { + RingerModeTracker ringerModeTracker, + PrivacyItemController privacyItemController) { mIconController = iconController; mCommandQueue = commandQueue; mBroadcastDispatcher = broadcastDispatcher; @@ -181,6 +191,7 @@ public class PhoneStatusBarPolicy mProvisionedController = deviceProvisionedController; mKeyguardStateController = keyguardStateController; mLocationController = locationController; + mPrivacyItemController = privacyItemController; mSensorPrivacyController = sensorPrivacyController; mRecordingController = recordingController; mUiBgExecutor = uiBgExecutor; @@ -200,6 +211,8 @@ public class PhoneStatusBarPolicy mSlotHeadset = resources.getString(com.android.internal.R.string.status_bar_headset); mSlotDataSaver = resources.getString(com.android.internal.R.string.status_bar_data_saver); mSlotLocation = resources.getString(com.android.internal.R.string.status_bar_location); + mSlotMicrophone = resources.getString(com.android.internal.R.string.status_bar_microphone); + mSlotCamera = resources.getString(com.android.internal.R.string.status_bar_camera); mSlotSensorsOff = resources.getString(com.android.internal.R.string.status_bar_sensors_off); mSlotScreenRecord = resources.getString( com.android.internal.R.string.status_bar_screen_record); @@ -271,6 +284,13 @@ public class PhoneStatusBarPolicy mResources.getString(R.string.accessibility_data_saver_on)); mIconController.setIconVisibility(mSlotDataSaver, false); + // privacy items + mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(), + mResources.getString(PrivacyType.TYPE_MICROPHONE.getNameId())); + mIconController.setIconVisibility(mSlotMicrophone, false); + mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(), + mResources.getString(PrivacyType.TYPE_CAMERA.getNameId())); + mIconController.setIconVisibility(mSlotCamera, false); mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID, mResources.getString(R.string.accessibility_location_active)); mIconController.setIconVisibility(mSlotLocation, false); @@ -294,6 +314,7 @@ public class PhoneStatusBarPolicy mNextAlarmController.addCallback(mNextAlarmCallback); mDataSaver.addCallback(this); mKeyguardStateController.addCallback(this); + mPrivacyItemController.addCallback(this); mSensorPrivacyController.addCallback(mSensorPrivacyListener); mLocationController.addCallback(this); mRecordingController.addCallback(this); @@ -609,9 +630,44 @@ public class PhoneStatusBarPolicy mIconController.setIconVisibility(mSlotDataSaver, isDataSaving); } + @Override // PrivacyItemController.Callback + public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) { + updatePrivacyItems(privacyItems); + } + + private void updatePrivacyItems(List<PrivacyItem> items) { + boolean showCamera = false; + boolean showMicrophone = false; + boolean showLocation = false; + for (PrivacyItem item : items) { + if (item == null /* b/124234367 */) { + Log.e(TAG, "updatePrivacyItems - null item found"); + StringWriter out = new StringWriter(); + mPrivacyItemController.dump(null, new PrintWriter(out), null); + // Throw so we can look into this + throw new NullPointerException(out.toString()); + } + switch (item.getPrivacyType()) { + case TYPE_CAMERA: + showCamera = true; + break; + case TYPE_LOCATION: + showLocation = true; + break; + case TYPE_MICROPHONE: + showMicrophone = true; + break; + } + } + + mIconController.setIconVisibility(mSlotCamera, showCamera); + mIconController.setIconVisibility(mSlotMicrophone, showMicrophone); + mIconController.setIconVisibility(mSlotLocation, showLocation); + } + @Override public void onLocationActiveChanged(boolean active) { - updateLocation(); + if (!mPrivacyItemController.getIndicatorsAvailable()) updateLocation(); } // Updates the status view based on the current state of location requests. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c5acd9bd0f06..c5571e8ceb20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -18,9 +18,6 @@ package com.android.systemui.statusbar.phone; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; -import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; -import static android.app.StatusBarManager.DISABLE_CLOCK; -import static android.app.StatusBarManager.DISABLE_NOTIFICATION_ICONS; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.WindowType; @@ -1101,7 +1098,6 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener()); mHeadsUpManager.addListener(mVisualStabilityManager); mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager); - mNotificationLogger.setHeadsUpManager(mHeadsUpManager); createNavigationBar(result); @@ -4175,7 +4171,6 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { mTopHidesStatusBar = topAppHidesStatusBar; - updateStatusBarIcons(topAppHidesStatusBar); if (!topAppHidesStatusBar && mWereIconsJustHidden) { // Immediately update the icon hidden state, since that should only apply if we're // staying fullscreen. @@ -4185,17 +4180,6 @@ public class StatusBar extends SystemUI implements DemoMode, updateHideIconsForBouncer(true /* animate */); } - private void updateStatusBarIcons(boolean topAppHidesStatusBar) { - int flags1 = StatusBarManager.DISABLE_NONE; - int flags2 = StatusBarManager.DISABLE2_NONE; - if (topAppHidesStatusBar) { - flags1 = DISABLE_NOTIFICATION_ICONS | DISABLE_CLOCK; - flags2 = DISABLE2_SYSTEM_ICONS; - } - - mCommandQueue.disable(mDisplayId, flags1, flags2, false); - } - protected void toggleKeyboardShortcuts(int deviceId) { KeyboardShortcuts.toggle(mContext, deviceId); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java index 6dd96f92b344..95601955ec04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.res.TypedArray; import android.graphics.Rect; +import android.icu.text.DateTimePatternGenerator; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; @@ -53,8 +54,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; -import libcore.icu.LocaleData; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; @@ -391,15 +390,16 @@ public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.C private final CharSequence getSmallTime() { Context context = getContext(); boolean is24 = DateFormat.is24HourFormat(context, mCurrentUserId); - LocaleData d = LocaleData.get(context.getResources().getConfiguration().locale); + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance( + context.getResources().getConfiguration().locale); final char MAGIC1 = '\uEF00'; final char MAGIC2 = '\uEF01'; SimpleDateFormat sdf; String format = mShowSeconds - ? is24 ? d.timeFormat_Hms : d.timeFormat_hms - : is24 ? d.timeFormat_Hm : d.timeFormat_hm; + ? is24 ? dtpg.getBestPattern("Hms") : dtpg.getBestPattern("hms") + : is24 ? dtpg.getBestPattern("Hm") : dtpg.getBestPattern("hm"); if (!format.equals(mClockFormatString)) { mContentDescriptionFormat = new SimpleDateFormat(format); /* diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 251693e162d0..adfc14e1d72b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -39,6 +39,7 @@ import com.android.systemui.BootCompleteCache; import com.android.systemui.appops.AppOpItem; import com.android.systemui.appops.AppOpsController; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.util.Utils; @@ -65,8 +66,8 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio @Inject public LocationControllerImpl(Context context, AppOpsController appOpsController, - @Main Looper mainLooper, BroadcastDispatcher broadcastDispatcher, - BootCompleteCache bootCompleteCache) { + @Main Looper mainLooper, @Background Handler backgroundHandler, + BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) { mContext = context; mAppOpsController = appOpsController; mBootCompleteCache = bootCompleteCache; @@ -80,7 +81,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio mAppOpsController.addCallback(new int[]{OP_MONITOR_HIGH_POWER_LOCATION}, this); // Examine the current location state and initialize the status view. - updateActiveLocationRequests(); + backgroundHandler.post(this::updateActiveLocationRequests); } /** diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java index 7561af770298..b1241b160d70 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java +++ b/packages/SystemUI/src/com/android/systemui/usb/UsbDebuggingActivity.java @@ -70,6 +70,8 @@ public class UsbDebuggingActivity extends AlertActivity if (SystemProperties.getInt("service.adb.tcp.port", 0) == 0) { mDisconnectedReceiver = new UsbDisconnectedReceiver(this); + IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); + mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); } Intent intent = getIntent(); @@ -119,6 +121,7 @@ public class UsbDebuggingActivity extends AlertActivity } boolean connected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); if (!connected) { + Log.d(TAG, "USB disconnected, notifying service"); notifyService(false); mActivity.finish(); } @@ -126,29 +129,20 @@ public class UsbDebuggingActivity extends AlertActivity } @Override - public void onStart() { - super.onStart(); - if (mDisconnectedReceiver != null) { - IntentFilter filter = new IntentFilter(UsbManager.ACTION_USB_STATE); - mBroadcastDispatcher.registerReceiver(mDisconnectedReceiver, filter); - } - } - - @Override - protected void onStop() { + protected void onDestroy() { if (mDisconnectedReceiver != null) { mBroadcastDispatcher.unregisterReceiver(mDisconnectedReceiver); } - super.onStop(); - } - - @Override - protected void onDestroy() { - // If the ADB service has not yet been notified due to this dialog being closed in some - // other way then notify the service to deny the connection to ensure system_server sends - // a response to adbd. - if (!mServiceNotified) { - notifyService(false); + // Only notify the service if the activity is finishing; if onDestroy has been called due to + // a configuration change then allow the user to still authorize the connection the next + // time the activity is in the foreground. + if (isFinishing()) { + // If the ADB service has not yet been notified due to this dialog being closed in some + // other way then notify the service to deny the connection to ensure system_server + // sends a response to adbd. + if (!mServiceNotified) { + notifyService(false); + } } super.onDestroy(); } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java new file mode 100644 index 000000000000..84ab66b66a7c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettings.java @@ -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.settings; + +/** + * Public interface that can be injected to interact with Settings.Global. + * + * See {@link SettingsProxy} for details. + */ +public interface GlobalSettings extends SettingsProxy { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java new file mode 100644 index 000000000000..1a30b0a8d8bf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.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.systemui.util.settings; + +import android.content.ContentResolver; +import android.net.Uri; +import android.provider.Settings; + +import javax.inject.Inject; + +class GlobalSettingsImpl implements GlobalSettings { + private final ContentResolver mContentResolver; + + @Inject + GlobalSettingsImpl(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Uri getUriFor(String name) { + return Settings.Global.getUriFor(name); + } + + @Override + public String getStringForUser(String name, int userHandle) { + return Settings.Global.getStringForUser(mContentResolver, name, userHandle); + } + + @Override + public boolean putString(String name, String value, boolean overrideableByRestore) { + throw new UnsupportedOperationException( + "This method only exists publicly for Settings.System and Settings.Secure"); + } + + @Override + public boolean putStringForUser(String name, String value, int userHandle) { + return Settings.Global.putStringForUser(mContentResolver, name, value, userHandle); + } + + @Override + public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, + int userHandle, boolean overrideableByRestore) { + return Settings.Global.putStringForUser( + mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore); + } + + @Override + public boolean putString(String name, String value, String tag, boolean makeDefault) { + return Settings.Global.putString(mContentResolver, name, value, tag, makeDefault); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java new file mode 100644 index 000000000000..798033e841d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettings.java @@ -0,0 +1,26 @@ +/* + * 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.settings; + +/** + * Public interface that can be injected to interact with Settings.Secure. + * + * See {@link SettingsProxy} for details. + */ + +public interface SecureSettings extends SettingsProxy { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java new file mode 100644 index 000000000000..020c234191e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -0,0 +1,69 @@ +/* + * 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.settings; + +import android.content.ContentResolver; +import android.net.Uri; +import android.provider.Settings; + +import javax.inject.Inject; + +class SecureSettingsImpl implements SecureSettings { + private final ContentResolver mContentResolver; + + @Inject + SecureSettingsImpl(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Uri getUriFor(String name) { + return Settings.Secure.getUriFor(name); + } + + @Override + public String getStringForUser(String name, int userHandle) { + return Settings.Secure.getStringForUser(mContentResolver, name, userHandle); + } + + @Override + public boolean putString(String name, String value, boolean overrideableByRestore) { + return Settings.Secure.putString(mContentResolver, name, value, overrideableByRestore); + } + + @Override + public boolean putStringForUser(String name, String value, int userHandle) { + return Settings.Secure.putStringForUser(mContentResolver, name, value, userHandle); + } + + @Override + public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, + int userHandle, boolean overrideableByRestore) { + return Settings.Secure.putStringForUser( + mContentResolver, name, value, tag, makeDefault, userHandle, overrideableByRestore); + } + + @Override + public boolean putString(String name, String value, String tag, boolean makeDefault) { + return Settings.Secure.putString(mContentResolver, name, value, tag, makeDefault); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java new file mode 100644 index 000000000000..5c37f797b678 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java @@ -0,0 +1,412 @@ +/* + * 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.settings; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.provider.Settings; + +/** + * Used to interact with Settings.Secure, Settings.Global, and Settings.System. + * + * This interface can be implemented to give instance method (instead of static method) versions + * of Settings.Secure, Settings.Global, and Settings.System. It can be injected into class + * constructors and then faked or mocked as needed in tests. + * + * You can ask for {@link SecureSettings}, {@link GlobalSettings}, or {@link SystemSettings} to be + * injected as needed. + * + * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, + * normally found on {@link ContentResolver} instances, unifying setting related actions in one + * place. + */ +public interface SettingsProxy { + + /** + * Returns the {@link ContentResolver} this instance was constructed with. + */ + ContentResolver getContentResolver(); + + /** + * Returns the user id for the associated {@link ContentResolver}. + */ + default int getUserId() { + return getContentResolver().getUserId(); + } + + /** + * Construct the content URI for a particular name/value pair, + * useful for monitoring changes with a ContentObserver. + * @param name to look up in the table + * @return the corresponding content URI, or null if not present + */ + Uri getUriFor(String name); + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserver(String name, ContentObserver settingsObserver) { + registerContentObserverForUser(name, settingsObserver, getUserId()); + } + + /** + * Convenience wrapper around + * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} + * + * Implicitly calls {@link #getUriFor(String)} on the passed in name. + */ + default void registerContentObserverForUser( + String name, ContentObserver settingsObserver, int userHandle) { + getContentResolver().registerContentObserver( + getUriFor(name), false, settingsObserver, userHandle); + } + + /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ + default void unregisterContentObserver(ContentObserver settingsObserver) { + getContentResolver().unregisterContentObserver(settingsObserver); + } + + /** + * Look up a name in the database. + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + default String getString(String name) { + return getStringForUser(name, getUserId()); + } + + /**See {@link #getString(String)}. */ + String getStringForUser(String name, int userHandle); + + /** + * Store a name/value pair into the database. Values written by this method will be + * overridden if a restore happens in the future. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + boolean putString(String name, String value, boolean overrideableByRestore); + + /** + * Store a name/value pair into the database. + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + default boolean putString(String name, String value) { + return putStringForUser(name, value, getUserId()); + } + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(String name, String value, int userHandle); + + /** See {@link #putString(String, String)}. */ + boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); + + /** + * Store a name/value pair into the database. + * <p> + * The method takes an optional tag to associate with the setting + * which can be used to clear only settings made by your package and + * associated with this tag by passing the tag to {@link + * #resetToDefaults(String)}. Anyone can override + * the current tag. Also if another package changes the setting + * then the tag will be set to the one specified in the set call + * which can be null. Also any of the settings setters that do not + * take a tag as an argument effectively clears the tag. + * </p><p> + * For example, if you set settings A and B with tags T1 and T2 and + * another app changes setting A (potentially to the same value), it + * can assign to it a tag T3 (note that now the package that changed + * the setting is not yours). Now if you reset your changes for T1 and + * T2 only setting B will be reset and A not (as it was changed by + * another package) but since A did not change you are in the desired + * initial state. Now if the other app changes the value of A (assuming + * you registered an observer in the beginning) you would detect that + * the setting was changed by another app and handle this appropriately + * (ignore, set back to some value, etc). + * </p><p> + * Also the method takes an argument whether to make the value the + * default for this setting. If the system already specified a default + * value, then the one passed in here will <strong>not</strong> + * be set as the default. + * </p> + * + * @param name to store. + * @param value to associate with the name. + * @param tag to associate with the setting. + * @param makeDefault whether to make the value the default one. + * @return true if the value was set, false on database errors. + * + * @see #resetToDefaults(String) + * + */ + boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, + boolean makeDefault); + + /** + * Convenience function for retrieving a single secure settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. The default value will be returned if the setting is + * not defined or not an integer. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid integer. + */ + default int getInt(String name, int def) { + return getIntForUser(name, def, getUserId()); + } + + /** See {@link #getInt(String, int)}. */ + default int getIntForUser(String name, int def, int userHandle) { + String v = getStringForUser(name, userHandle); + try { + return v != null ? Integer.parseInt(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single secure settings value + * as an integer. Note that internally setting values are always + * stored as strings; this function converts the string to an integer + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link Settings.SettingNotFoundException}. + * + * @param name The name of the setting to retrieve. + * + * @throws Settings.SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + * + * @return The setting's current value. + */ + default int getInt(String name) throws Settings.SettingNotFoundException { + return getIntForUser(name, getUserId()); + } + + /** See {@link #getInt(String)}. */ + default int getIntForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + try { + return Integer.parseInt(v); + } catch (NumberFormatException e) { + throw new Settings.SettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as an + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + default boolean putInt(String name, int value) { + return putIntForUser(name, value, getUserId()); + } + /** See {@link #putInt(String, int)}. */ + default boolean putIntForUser(String name, int value, int userHandle) { + return putStringForUser(name, Integer.toString(value), userHandle); + } + + /** + * Convenience function for retrieving a single secure settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. The default value will be returned if the setting is + * not defined or not a {@code long}. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid {@code long}. + */ + default long getLong(String name, long def) { + return getLongForUser(name, def, getUserId()); + } + + /** See {@link #getLong(String, long)}. */ + default long getLongForUser(String name, long def, int userHandle) { + String valString = getStringForUser(name, userHandle); + long value; + try { + value = valString != null ? Long.parseLong(valString) : def; + } catch (NumberFormatException e) { + value = def; + } + return value; + } + + /** + * Convenience function for retrieving a single secure settings value + * as a {@code long}. Note that internally setting values are always + * stored as strings; this function converts the string to a {@code long} + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link Settings.SettingNotFoundException}. + * + * @param name The name of the setting to retrieve. + * + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not an integer. + */ + default long getLong(String name) throws Settings.SettingNotFoundException { + return getLongForUser(name, getUserId()); + } + + /** See {@link #getLong(String)}. */ + default long getLongForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String valString = getStringForUser(name, userHandle); + try { + return Long.parseLong(valString); + } catch (NumberFormatException e) { + throw new Settings.SettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a secure settings value as a long + * integer. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + default boolean putLong(String name, long value) { + return putLongForUser(name, value, getUserId()); + } + + /** See {@link #putLong(String, long)}. */ + default boolean putLongForUser(String name, long value, int userHandle) { + return putStringForUser(name, Long.toString(value), userHandle); + } + + /** + * Convenience function for retrieving a single secure settings value + * as a floating point number. Note that internally setting values are + * always stored as strings; this function converts the string to an + * float for you. The default value will be returned if the setting + * is not defined or not a valid float. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * + * @return The setting's current value, or 'def' if it is not defined + * or not a valid float. + */ + default float getFloat(String name, float def) { + return getFloatForUser(name, def, getUserId()); + } + + /** See {@link #getFloat(String)}. */ + default float getFloatForUser(String name, float def, int userHandle) { + String v = getStringForUser(name, userHandle); + try { + return v != null ? Float.parseFloat(v) : def; + } catch (NumberFormatException e) { + return def; + } + } + + /** + * Convenience function for retrieving a single secure settings value + * as a float. Note that internally setting values are always + * stored as strings; this function converts the string to a float + * for you. + * <p> + * This version does not take a default value. If the setting has not + * been set, or the string value is not a number, + * it throws {@link Settings.SettingNotFoundException}. + * + * @param name The name of the setting to retrieve. + * + * @throws Settings.SettingNotFoundException Thrown if a setting by the given + * name can't be found or the setting value is not a float. + * + * @return The setting's current value. + */ + default float getFloat(String name) throws Settings.SettingNotFoundException { + return getFloatForUser(name, getUserId()); + } + + /** See {@link #getFloat(String, float)}. */ + default float getFloatForUser(String name, int userHandle) + throws Settings.SettingNotFoundException { + String v = getStringForUser(name, userHandle); + if (v == null) { + throw new Settings.SettingNotFoundException(name); + } + try { + return Float.parseFloat(v); + } catch (NumberFormatException e) { + throw new Settings.SettingNotFoundException(name); + } + } + + /** + * Convenience function for updating a single settings value as a + * floating point number. This will either create a new entry in the + * table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values + * are always stored as strings, so this function converts the given + * value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + default boolean putFloat(String name, float value) { + return putFloatForUser(name, value, getUserId()); + } + + /** See {@link #putFloat(String, float)} */ + default boolean putFloatForUser(String name, float value, int userHandle) { + return putStringForUser(name, Float.toString(value), userHandle); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java new file mode 100644 index 000000000000..f36c335e0f44 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java @@ -0,0 +1,39 @@ +/* + * 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.settings; + +import dagger.Binds; +import dagger.Module; + +/** + * Dagger Module for classes within com.android.systemui.util.settings. + */ +@Module +public interface SettingsUtilModule { + + /** Bind SecureSettingsImpl to SecureSettings. */ + @Binds + SecureSettings bindsSecureSettings(SecureSettingsImpl impl); + + /** Bind SystemSettingsImpl to SystemSettings. */ + @Binds + SystemSettings bindsSystemSettings(SystemSettingsImpl impl); + + /** Bind GlobalSettingsImpl to GlobalSettings. */ + @Binds + GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java new file mode 100644 index 000000000000..d57d7496381c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettings.java @@ -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.settings; + +/** + * Public interface that can be injected to interact with Settings.System. + * + * See {@link SettingsProxy} for details. + */ +public interface SystemSettings extends SettingsProxy { +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java new file mode 100644 index 000000000000..0dbb76f8f758 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.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.systemui.util.settings; + +import android.content.ContentResolver; +import android.net.Uri; +import android.provider.Settings; + +import javax.inject.Inject; + +class SystemSettingsImpl implements SystemSettings { + private final ContentResolver mContentResolver; + + @Inject + SystemSettingsImpl(ContentResolver contentResolver) { + mContentResolver = contentResolver; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Uri getUriFor(String name) { + return Settings.System.getUriFor(name); + } + + @Override + public String getStringForUser(String name, int userHandle) { + return Settings.System.getStringForUser(mContentResolver, name, userHandle); + } + + @Override + public boolean putString(String name, String value, boolean overrideableByRestore) { + return Settings.System.putString(mContentResolver, name, value, overrideableByRestore); + } + + @Override + public boolean putStringForUser(String name, String value, int userHandle) { + return Settings.System.putStringForUser(mContentResolver, name, value, userHandle); + } + + @Override + public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, + int userHandle, boolean overrideableByRestore) { + throw new UnsupportedOperationException( + "This method only exists publicly for Settings.Secure and Settings.Global"); + } + + @Override + public boolean putString(String name, String value, String tag, boolean makeDefault) { + throw new UnsupportedOperationException( + "This method only exists publicly for Settings.Secure and Settings.Global"); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java new file mode 100644 index 000000000000..fbc167683a2a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WindowManagerShellModule.java @@ -0,0 +1,67 @@ +/* + * 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.wmshell; + +import android.content.Context; +import android.os.Handler; +import android.view.IWindowManager; + +import com.android.systemui.dagger.qualifiers.Main; +import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayImeController; +import com.android.wm.shell.common.SystemWindows; +import com.android.wm.shell.common.TransactionPool; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +/** + * Provides dependencies from {@link com.android.wm.shell}. + */ +@Module +// TODO(b/161116823) Clean up dependencies after wm shell migration finished. +public class WindowManagerShellModule { + @Singleton + @Provides + static TransactionPool provideTransactionPool() { + return new TransactionPool(); + } + + @Singleton + @Provides + static DisplayController provideDisplayController(Context context, @Main Handler handler, + IWindowManager wmService) { + return new DisplayController(context, handler, wmService); + } + + @Singleton + @Provides + static SystemWindows provideSystemWindows(DisplayController displayController, + IWindowManager wmService) { + return new SystemWindows(displayController, wmService); + } + + @Singleton + @Provides + static DisplayImeController provideDisplayImeController( + IWindowManager wmService, DisplayController displayController, + @Main Handler mainHandler, TransactionPool transactionPool) { + return new DisplayImeController(wmService, displayController, mainHandler, transactionPool); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java new file mode 100644 index 000000000000..9cb4fb319fa2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/DisplayIdIndexSupplierTest.java @@ -0,0 +1,79 @@ +/* + * 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.accessibility; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; + +import android.hardware.display.DisplayManager; +import android.testing.AndroidTestingRunner; +import android.view.Display; + +import androidx.annotation.NonNull; +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class DisplayIdIndexSupplierTest extends SysuiTestCase { + + private DisplayIdIndexSupplier<Object> mDisplayIdIndexSupplier; + + @Before + public void setUp() throws Exception { + mDisplayIdIndexSupplier = new DisplayIdIndexSupplier( + mContext.getSystemService(DisplayManager.class)) { + + @NonNull + @Override + protected Object createInstance(Display display) { + return new Object(); + } + }; + } + + @Test + public void get_instanceIsNotNull() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + assertNotNull(object); + } + + @Test + public void get_removeExistedObject_newObject() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + mDisplayIdIndexSupplier.remove(Display.DEFAULT_DISPLAY); + + Object newObject = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + + assertNotEquals(object, newObject); + } + + @Test + public void get_clearAllObjects_newObject() { + Object object = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + mDisplayIdIndexSupplier.clear(); + + Object newObject = mDisplayIdIndexSupplier.get(Display.DEFAULT_DISPLAY); + + assertNotEquals(object, newObject); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java index d6d2fcd9610a..69482791ef23 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java @@ -16,20 +16,13 @@ package com.android.systemui.accessibility; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.content.Context; import android.provider.Settings; import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; import android.view.Display; -import android.view.View; -import android.view.WindowManager; -import android.view.WindowMetrics; import androidx.test.filters.SmallTest; @@ -38,45 +31,43 @@ import com.android.systemui.SysuiTestCase; 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; @SmallTest @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper /** Tests the ModeSwitchesController. */ public class ModeSwitchesControllerTest extends SysuiTestCase { - private WindowManager mWindowManager; + @Mock + private ModeSwitchesController.SwitchSupplier mSupplier; + @Mock + private MagnificationModeSwitch mModeSwitch; private ModeSwitchesController mModeSwitchesController; + @Before public void setUp() { - mWindowManager = mock(WindowManager.class); - Display display = mContext.getSystemService(WindowManager.class).getDefaultDisplay(); - when(mWindowManager.getDefaultDisplay()).thenReturn(display); - WindowMetrics metrics = mContext.getSystemService(WindowManager.class) - .getMaximumWindowMetrics(); - when(mWindowManager.getMaximumWindowMetrics()).thenReturn(metrics); - mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); - mModeSwitchesController = new ModeSwitchesController(mContext); + MockitoAnnotations.initMocks(this); + when(mSupplier.get(anyInt())).thenReturn(mModeSwitch); + mModeSwitchesController = new ModeSwitchesController(mSupplier); } @Test public void testShowButton() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - verify(mWindowManager).addView(any(), any()); + + verify(mModeSwitch).showButton(Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); } @Test public void testRemoveButton() { mModeSwitchesController.showButton(Display.DEFAULT_DISPLAY, Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); - ArgumentCaptor<View> captor = ArgumentCaptor.forClass(View.class); - verify(mWindowManager).addView(captor.capture(), any(WindowManager.LayoutParams.class)); mModeSwitchesController.removeButton(Display.DEFAULT_DISPLAY); - verify(mWindowManager).removeView(eq(captor.getValue())); + verify(mModeSwitch).removeButton(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java index e0049d1349f1..4fdc06e64e2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java @@ -26,11 +26,14 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; 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.AppOpsManager; +import android.content.pm.PackageManager; import android.os.Looper; import android.os.UserHandle; import android.testing.AndroidTestingRunner; @@ -56,6 +59,7 @@ public class AppOpsControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = UserHandle.getUid(0, 0); private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0); + private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0); @Mock private AppOpsManager mAppOpsManager; @@ -65,6 +69,10 @@ public class AppOpsControllerTest extends SysuiTestCase { private AppOpsControllerImpl.H mMockHandler; @Mock private DumpManager mDumpManager; + @Mock + private PermissionFlagsCache mFlagsCache; + @Mock + private PackageManager mPackageManager; private AppOpsControllerImpl mController; private TestableLooper mTestableLooper; @@ -76,8 +84,22 @@ public class AppOpsControllerTest extends SysuiTestCase { getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager); - mController = - new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpManager); + // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of + // TEST_UID_NON_USER_SENSITIVE are user sensitive. + getContext().setMockPackageManager(mPackageManager); + when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID))).thenReturn( + PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); + when(mFlagsCache.getPermissionFlags(anyString(), anyString(), eq(TEST_UID_OTHER))) + .thenReturn(PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED); + when(mFlagsCache.getPermissionFlags(anyString(), anyString(), + eq(TEST_UID_NON_USER_SENSITIVE))).thenReturn(0); + + mController = new AppOpsControllerImpl( + mContext, + mTestableLooper.getLooper(), + mDumpManager, + mFlagsCache + ); } @Test @@ -173,6 +195,26 @@ public class AppOpsControllerTest extends SysuiTestCase { } @Test + public void nonUserSensitiveOpsAreIgnored() { + mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); + assertEquals(0, mController.getActiveAppOpsForUser( + UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size()); + } + + @Test + public void nonUserSensitiveOpsNotNotified() { + mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback); + mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO, + TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true); + + mTestableLooper.processAllMessages(); + + verify(mCallback, never()) + .onActiveStateChanged(anyInt(), anyInt(), anyString(), anyBoolean()); + } + + @Test public void opNotedScheduledForRemoval() { mController.setBGHandler(mMockHandler); mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt new file mode 100644 index 000000000000..0fb0ce087ee3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt @@ -0,0 +1,145 @@ +/* + * 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.appops + +import android.content.pm.PackageManager +import android.os.UserHandle +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class PermissionFlagsCacheTest : SysuiTestCase() { + + companion object { + const val TEST_PERMISSION = "test_permission" + const val TEST_PACKAGE = "test_package" + const val TEST_UID1 = 1000 + const val TEST_UID2 = UserHandle.PER_USER_RANGE + 1000 + } + + @Mock + private lateinit var packageManager: PackageManager + + private lateinit var executor: FakeExecutor + private lateinit var flagsCache: PermissionFlagsCache + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + executor = FakeExecutor(FakeSystemClock()) + + flagsCache = PermissionFlagsCache(packageManager, executor) + executor.runAllReady() + } + + @Test + fun testNotListeningByDefault() { + verify(packageManager, never()).addOnPermissionsChangeListener(any()) + } + + @Test + fun testGetCorrectFlags() { + `when`(packageManager.getPermissionFlags(anyString(), anyString(), any())).thenReturn(0) + `when`(packageManager.getPermissionFlags( + TEST_PERMISSION, + TEST_PACKAGE, + UserHandle.getUserHandleForUid(TEST_UID1)) + ).thenReturn(1) + + assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)) + assertNotEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2)) + } + + @Test + fun testFlagIsCached() { + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1) + + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1) + + verify(packageManager, times(1)).getPermissionFlags( + TEST_PERMISSION, + TEST_PACKAGE, + UserHandle.getUserHandleForUid(TEST_UID1) + ) + } + + @Test + fun testListeningAfterFirstRequest() { + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1) + + verify(packageManager).addOnPermissionsChangeListener(any()) + } + + @Test + fun testListeningOnlyOnce() { + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1) + + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID2) + + verify(packageManager, times(1)).addOnPermissionsChangeListener(any()) + } + + @Test + fun testUpdateFlag() { + assertEquals(0, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)) + + `when`(packageManager.getPermissionFlags( + TEST_PERMISSION, + TEST_PACKAGE, + UserHandle.getUserHandleForUid(TEST_UID1)) + ).thenReturn(1) + + flagsCache.onPermissionsChanged(TEST_UID1) + + executor.runAllReady() + + assertEquals(1, flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1)) + } + + @Test + fun testUpdateFlag_notUpdatedIfUidHasNotBeenRequestedBefore() { + flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, TEST_UID1) + + flagsCache.onPermissionsChanged(TEST_UID2) + + executor.runAllReady() + + verify(packageManager, never()).getPermissionFlags( + TEST_PERMISSION, + TEST_PACKAGE, + UserHandle.getUserHandleForUid(TEST_UID2) + ) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index e8a0c738f966..d4a94c5b9e66 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -45,6 +45,7 @@ import android.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.face.FaceManager; +import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -530,6 +531,11 @@ public class AuthControllerTest extends SysuiTestCase { IActivityTaskManager getActivityTaskManager() { return mock(IActivityTaskManager.class); } + + @Override + FingerprintManager getFingerprintManager(Context context) { + return mock(FingerprintManager.class); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java index 7ebead8a33fa..4f0ff4242b6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java @@ -47,6 +47,7 @@ import com.android.systemui.plugins.SensorManagerPlugin; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.util.sensors.AsyncSensorManager; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.wakelock.WakeLock; import org.junit.Before; @@ -84,6 +85,7 @@ public class DozeSensorsTest extends SysuiTestCase { private DozeLog mDozeLog; @Mock private ProximitySensor mProximitySensor; + private FakeSettings mFakeSettings = new FakeSettings(); private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener; private TestableLooper mTestableLooper; private DozeSensors mDozeSensors; @@ -154,7 +156,7 @@ public class DozeSensorsTest extends SysuiTestCase { TestableDozeSensors() { super(getContext(), mSensorManager, mDozeParameters, mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog, - mProximitySensor); + mProximitySensor, mFakeSettings); for (TriggerSensor sensor : mSensors) { if (sensor instanceof PluginSensor && ((PluginSensor) sensor).mPluginSensor.getType() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index d3af835873e2..1ed58714fb9f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -47,6 +47,7 @@ import com.android.systemui.util.sensors.FakeProximitySensor; import com.android.systemui.util.sensors.FakeSensorManager; import com.android.systemui.util.sensors.FakeThresholdSensor; import com.android.systemui.util.sensors.ProximitySensor; +import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.time.FakeSystemClock; import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.util.wakelock.WakeLockFake; @@ -99,7 +100,7 @@ public class DozeTriggersTest extends SysuiTestCase { mTriggers = new DozeTriggers(mContext, mHost, mAlarmManager, config, parameters, asyncSensorManager, wakeLock, mDockManager, mProximitySensor, - mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher); + mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings()); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java index 84a261b6e7d2..3231b2852e7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -41,7 +41,7 @@ import android.window.WindowContainerToken; import androidx.test.filters.SmallTest; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import org.junit.Before; import org.junit.Ignore; diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java index 180c4507bd09..3b284b14c36f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedGestureHandlerTest.java @@ -33,7 +33,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.systemui.model.SysUiState; import com.android.systemui.statusbar.phone.NavigationModeController; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java index b6b2217837b2..55bec54eacb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedManagerImplTest.java @@ -33,7 +33,7 @@ import android.view.Display; import androidx.test.filters.SmallTest; import com.android.systemui.model.SysUiState; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java index 80fe0f095020..3a4ba6a213dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/onehanded/OneHandedTouchHandlerTest.java @@ -31,7 +31,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.systemui.model.SysUiState; import com.android.systemui.statusbar.phone.NavigationModeController; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import org.junit.Before; import org.junit.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 70c2bba040a0..e9d2b73182e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java @@ -33,7 +33,7 @@ import android.view.Gravity; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.wm.DisplayController; +import com.android.wm.shell.common.DisplayController; import org.junit.Before; import org.junit.Test; diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt new file mode 100644 index 000000000000..dcee5a716ceb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt @@ -0,0 +1,77 @@ +/* + * 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.privacy + +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.systemui.SysuiTestCase +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class PrivacyChipBuilderTest : SysuiTestCase() { + + companion object { + val TEST_UID = 1 + } + + @Test + fun testGenerateAppsList() { + val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( + "Bar", TEST_UID)) + val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication( + "Bar", TEST_UID)) + val foo0 = PrivacyItem(Privacy.TYPE_MICROPHONE, PrivacyApplication( + "Foo", TEST_UID)) + val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( + "Baz", TEST_UID)) + + val items = listOf(bar2, foo0, baz1, bar3) + + val textBuilder = PrivacyChipBuilder(context, items) + + val list = textBuilder.appsAndTypes + assertEquals(3, list.size) + val appsList = list.map { it.first } + val typesList = list.map { it.second } + // List is sorted by number of types and then by types + assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName }) + assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0]) + assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1]) + assertEquals(listOf(Privacy.TYPE_MICROPHONE), typesList[2]) + } + + @Test + fun testOrder() { + // We want location to always go last, so it will go in the "+ other apps" + val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA, + PrivacyApplication("Camera", TEST_UID)) + val appMicrophone = + PrivacyItem(PrivacyType.TYPE_MICROPHONE, + PrivacyApplication("Microphone", TEST_UID)) + val appLocation = + PrivacyItem(PrivacyType.TYPE_LOCATION, + PrivacyApplication("Location", TEST_UID)) + + val items = listOf(appLocation, appMicrophone, appCamera) + val textBuilder = PrivacyChipBuilder(context, items) + val appList = textBuilder.appsAndTypes.map { it.first }.map { it.packageName } + assertEquals(listOf("Camera", "Microphone", "Location"), appList) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt new file mode 100644 index 000000000000..dddc35072315 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -0,0 +1,290 @@ +/* + * 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.privacy + +import android.app.ActivityManager +import android.app.AppOpsManager +import android.content.Context +import android.content.Intent +import android.content.pm.UserInfo +import android.os.UserHandle +import android.os.UserManager +import android.provider.DeviceConfig +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import androidx.test.filters.SmallTest +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.systemui.SysuiTestCase +import com.android.systemui.appops.AppOpItem +import com.android.systemui.appops.AppOpsController +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dump.DumpManager +import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.util.DeviceConfigProxyFake +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import org.hamcrest.Matchers.hasItem +import org.hamcrest.Matchers.not +import org.hamcrest.Matchers.nullValue +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThat +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.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyList +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.reset +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +@RunWithLooper +class PrivacyItemControllerTest : SysuiTestCase() { + + companion object { + val CURRENT_USER_ID = ActivityManager.getCurrentUser() + val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE + const val SYSTEM_UID = 1000 + const val TEST_PACKAGE_NAME = "test" + const val DEVICE_SERVICES_STRING = "Device services" + const val TAG = "PrivacyItemControllerTest" + fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() + fun <T> eq(value: T): T = Mockito.eq(value) ?: value + fun <T> any(): T = Mockito.any<T>() + } + + @Mock + private lateinit var appOpsController: AppOpsController + @Mock + private lateinit var callback: PrivacyItemController.Callback + @Mock + private lateinit var userManager: UserManager + @Mock + private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock + private lateinit var dumpManager: DumpManager + @Captor + private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>> + @Captor + private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback> + + private lateinit var privacyItemController: PrivacyItemController + private lateinit var executor: FakeExecutor + private lateinit var deviceConfigProxy: DeviceConfigProxy + + fun PrivacyItemController(context: Context): PrivacyItemController { + return PrivacyItemController( + context, + appOpsController, + executor, + executor, + broadcastDispatcher, + deviceConfigProxy, + userManager, + dumpManager + ) + } + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + executor = FakeExecutor(FakeSystemClock()) + deviceConfigProxy = DeviceConfigProxyFake() + + appOpsController = mDependency.injectMockDependency(AppOpsController::class.java) + + deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, + "true", false) + + doReturn(listOf(object : UserInfo() { + init { + id = CURRENT_USER_ID + } + })).`when`(userManager).getProfiles(anyInt()) + + privacyItemController = PrivacyItemController(mContext) + } + + @Test + fun testSetListeningTrueByAddingCallback() { + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), + any()) + verify(callback).onPrivacyItemsChanged(anyList()) + } + + @Test + fun testSetListeningFalseByRemovingLastCallback() { + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(appOpsController, never()).removeCallback(any(), + any()) + privacyItemController.removeCallback(callback) + executor.runAllReady() + verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS), + any()) + verify(callback).onPrivacyItemsChanged(emptyList()) + } + + @Test + fun testDistinctItems() { + doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), + AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) + .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(callback).onPrivacyItemsChanged(capture(argCaptor)) + assertEquals(1, argCaptor.value.size) + } + + @Test + fun testRegisterReceiver_allUsers() { + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(broadcastDispatcher, atLeastOnce()).registerReceiver( + eq(privacyItemController.userSwitcherReceiver), any(), eq(null), eq(UserHandle.ALL)) + verify(broadcastDispatcher, never()) + .unregisterReceiver(eq(privacyItemController.userSwitcherReceiver)) + } + + @Test + fun testReceiver_ACTION_USER_FOREGROUND() { + privacyItemController.userSwitcherReceiver.onReceive(context, + Intent(Intent.ACTION_USER_SWITCHED)) + executor.runAllReady() + verify(userManager).getProfiles(anyInt()) + } + + @Test + fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() { + privacyItemController.userSwitcherReceiver.onReceive(context, + Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)) + executor.runAllReady() + verify(userManager).getProfiles(anyInt()) + } + + @Test + fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() { + privacyItemController.userSwitcherReceiver.onReceive(context, + Intent(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) + executor.runAllReady() + verify(userManager).getProfiles(anyInt()) + } + + @Test + fun testAddMultipleCallbacks() { + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(callback).onPrivacyItemsChanged(anyList()) + + privacyItemController.addCallback(otherCallback) + executor.runAllReady() + verify(otherCallback).onPrivacyItemsChanged(anyList()) + // Adding a callback should not unnecessarily call previous ones + verifyNoMoreInteractions(callback) + } + + @Test + fun testMultipleCallbacksAreUpdated() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + executor.runAllReady() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + executor.runAllReady() + verify(callback).onPrivacyItemsChanged(anyList()) + verify(otherCallback).onPrivacyItemsChanged(anyList()) + } + + @Test + fun testRemoveCallback() { + doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + val otherCallback = mock(PrivacyItemController.Callback::class.java) + privacyItemController.addCallback(callback) + privacyItemController.addCallback(otherCallback) + executor.runAllReady() + executor.runAllReady() + reset(callback) + reset(otherCallback) + + verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) + privacyItemController.removeCallback(callback) + argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true) + executor.runAllReady() + verify(callback, never()).onPrivacyItemsChanged(anyList()) + verify(otherCallback).onPrivacyItemsChanged(anyList()) + } + + @Test + fun testListShouldNotHaveNull() { + doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0), + AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0))) + .`when`(appOpsController).getActiveAppOpsForUser(anyInt()) + privacyItemController.addCallback(callback) + executor.runAllReady() + executor.runAllReady() + + verify(callback).onPrivacyItemsChanged(capture(argCaptor)) + assertEquals(1, argCaptor.value.size) + assertThat(argCaptor.value, not(hasItem(nullValue()))) + } + + @Test + fun testListShouldBeCopy() { + val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA, + PrivacyApplication("", TEST_UID))) + privacyItemController.privacyList = list + val privacyList = privacyItemController.privacyList + assertEquals(list, privacyList) + assertTrue(list !== privacyList) + } + + @Test + fun testNotListeningWhenIndicatorsDisabled() { + deviceConfigProxy.setProperty( + DeviceConfig.NAMESPACE_PRIVACY, + SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, + "false", + false + ) + privacyItemController.addCallback(callback) + executor.runAllReady() + verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS), + any()) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java new file mode 100644 index 000000000000..4aaafbdaec1d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java @@ -0,0 +1,153 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; + +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; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.statusbar.phone.StatusBar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class ActionProxyReceiverTest extends SysuiTestCase { + + @Mock + private StatusBar mMockStatusBar; + @Mock + private ActivityManagerWrapper mMockActivityManagerWrapper; + @Mock + private Future mMockFuture; + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private PendingIntent mMockPendingIntent; + + private Intent mIntent; + + @Before + public void setup() throws InterruptedException, ExecutionException, TimeoutException { + MockitoAnnotations.initMocks(this); + mIntent = new Intent(mContext, ActionProxyReceiver.class) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent); + + when(mMockActivityManagerWrapper.closeSystemWindows(anyString())).thenReturn(mMockFuture); + when(mMockFuture.get(anyLong(), any(TimeUnit.class))).thenReturn(null); + } + + @Test + public void testPendingIntentSentWithoutStatusBar() throws PendingIntent.CanceledException { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(false); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); + verify(mMockStatusBar, never()).executeRunnableDismissingKeyguard( + any(Runnable.class), any(Runnable.class), anyBoolean(), anyBoolean(), anyBoolean()); + verify(mMockPendingIntent).send( + eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + } + + @Test + public void testPendingIntentSentWithStatusBar() throws PendingIntent.CanceledException { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + // ensure that the pending intent call is passed through + doAnswer((Answer<Object>) invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }).when(mMockStatusBar).executeRunnableDismissingKeyguard( + any(Runnable.class), isNull(), anyBoolean(), anyBoolean(), anyBoolean()); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockActivityManagerWrapper).closeSystemWindows(SYSTEM_DIALOG_REASON_SCREENSHOT); + verify(mMockStatusBar).executeRunnableDismissingKeyguard( + any(Runnable.class), isNull(), eq(true), eq(true), eq(true)); + verify(mMockPendingIntent).send( + eq(mContext), anyInt(), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + } + + @Test + public void testSmartActionsNotNotifiedByDefault() { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockScreenshotSmartActions, never()) + .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean()); + } + + @Test + public void testSmartActionsNotifiedIfEnabled() { + ActionProxyReceiver actionProxyReceiver = constructActionProxyReceiver(true); + mIntent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); + String testId = "testID"; + mIntent.putExtra(EXTRA_ID, testId); + + actionProxyReceiver.onReceive(mContext, mIntent); + + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, ACTION_TYPE_SHARE, false); + } + + private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) { + if (withStatusBar) { + return new ActionProxyReceiver( + Optional.of(mMockStatusBar), mMockActivityManagerWrapper, + mMockScreenshotSmartActions); + } else { + return new ActionProxyReceiver( + Optional.empty(), mMockActivityManagerWrapper, mMockScreenshotSmartActions); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java new file mode 100644 index 000000000000..b9249131c191 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java @@ -0,0 +1,145 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED; +import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.util.concurrent.Executor; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class DeleteScreenshotReceiverTest extends SysuiTestCase { + + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private Executor mMockExecutor; + + private DeleteScreenshotReceiver mDeleteScreenshotReceiver; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mDeleteScreenshotReceiver = + new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mMockExecutor); + } + + @Test + public void testNoUriProvided() { + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); + + mDeleteScreenshotReceiver.onReceive(mContext, intent); + + verify(mMockExecutor, never()).execute(any(Runnable.class)); + verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( + any(Context.class), any(String.class), any(String.class), anyBoolean()); + } + + @Test + public void testFileDeleted() { + DeleteScreenshotReceiver deleteScreenshotReceiver = + new DeleteScreenshotReceiver(mMockScreenshotSmartActions, mFakeExecutor); + ContentResolver contentResolver = mContext.getContentResolver(); + final Uri testUri = contentResolver.insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, getFakeContentValues()); + assertNotNull(testUri); + + try { + Cursor cursor = + contentResolver.query(testUri, null, null, null, null); + assertEquals(1, cursor.getCount()); + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class) + .putExtra(SCREENSHOT_URI_ID, testUri.toString()); + + deleteScreenshotReceiver.onReceive(mContext, intent); + int runCount = mFakeExecutor.runAllReady(); + + assertEquals(1, runCount); + cursor = + contentResolver.query(testUri, null, null, null, null); + assertEquals(0, cursor.getCount()); + } finally { + contentResolver.delete(testUri, null, null); + } + + // ensure smart actions not called by default + verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction( + any(Context.class), any(String.class), any(String.class), anyBoolean()); + } + + @Test + public void testNotifyScreenshotAction() { + Intent intent = new Intent(mContext, DeleteScreenshotReceiver.class); + String uriString = "testUri"; + String testId = "testID"; + intent.putExtra(SCREENSHOT_URI_ID, uriString); + intent.putExtra(EXTRA_ID, testId); + intent.putExtra(EXTRA_SMART_ACTIONS_ENABLED, true); + + mDeleteScreenshotReceiver.onReceive(mContext, intent); + + verify(mMockExecutor).execute(any(Runnable.class)); + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, ACTION_TYPE_DELETE, false); + } + + private static ContentValues getFakeContentValues() { + final ContentValues values = new ContentValues(); + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + + File.separator + Environment.DIRECTORY_SCREENSHOTS); + values.put(MediaStore.MediaColumns.DISPLAY_NAME, "test_screenshot"); + values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png"); + values.put(MediaStore.MediaColumns.DATE_ADDED, 0); + values.put(MediaStore.MediaColumns.DATE_MODIFIED, 0); + return values; + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index d3b33992d017..184329ec6e5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -61,12 +61,14 @@ import java.util.concurrent.TimeUnit; */ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { private ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; + private ScreenshotSmartActions mScreenshotSmartActions; private Handler mHandler; @Before public void setup() { mSmartActionsProvider = mock( ScreenshotNotificationSmartActionsProvider.class); + mScreenshotSmartActions = new ScreenshotSmartActions(); mHandler = mock(Handler.class); } @@ -82,7 +84,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { when(smartActionsProvider.getActions(any(), any(), any(), any(), any())) .thenThrow(RuntimeException.class); CompletableFuture<List<Notification.Action>> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://authority/data"), bitmap, smartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); assertNotNull(smartActionsFuture); @@ -100,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { int timeoutMs = 1000; when(smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS)).thenThrow( RuntimeException.class); - List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions( + List<Notification.Action> actions = mScreenshotSmartActions.getSmartActions( "", smartActionsFuture, timeoutMs, mSmartActionsProvider); assertEquals(Collections.emptyList(), actions); } @@ -111,7 +113,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { throws Exception { doThrow(RuntimeException.class).when(mSmartActionsProvider).notifyOp(any(), any(), any(), anyLong()); - ScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1); + mScreenshotSmartActions.notifyScreenshotOp(null, mSmartActionsProvider, null, null, -1); } // Tests for a non-hardware bitmap, ScreenshotNotificationSmartActionsProvider is never invoked @@ -122,7 +124,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.RGB_565); CompletableFuture<List<Notification.Action>> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any()); @@ -136,7 +138,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { public void testScreenshotNotificationSmartActionsProviderInvokedOnce() { Bitmap bitmap = mock(Bitmap.class); when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE); - ScreenshotSmartActions.getSmartActionsFuture( + mScreenshotSmartActions.getSmartActionsFuture( "", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any()); @@ -152,7 +154,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider( mContext, null, mHandler); CompletableFuture<List<Notification.Action>> smartActionsFuture = - ScreenshotSmartActions.getSmartActionsFuture("", null, bitmap, + mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap, actionsProvider, true, UserHandle.getUserHandleForUid(UserHandle.myUserId())); assertNotNull(smartActionsFuture); @@ -172,7 +174,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); @@ -198,7 +201,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); @@ -224,7 +228,8 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + SaveImageInBackgroundTask task = + new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data); Notification.Action deleteAction = task.createDeleteAction(mContext, mContext.getResources(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java new file mode 100644 index 000000000000..ce6f0736ec33 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java @@ -0,0 +1,76 @@ +/* + * 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.screenshot; + +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE; +import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.verify; + +import android.app.PendingIntent; +import android.content.Intent; +import android.os.Bundle; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class SmartActionsReceiverTest extends SysuiTestCase { + + @Mock + private ScreenshotSmartActions mMockScreenshotSmartActions; + @Mock + private PendingIntent mMockPendingIntent; + + private SmartActionsReceiver mSmartActionsReceiver; + private Intent mIntent; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mSmartActionsReceiver = new SmartActionsReceiver(mMockScreenshotSmartActions); + mIntent = new Intent(mContext, SmartActionsReceiver.class) + .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, mMockPendingIntent); + } + + @Test + public void testSmartActionIntent() throws PendingIntent.CanceledException { + String testId = "testID"; + String testActionType = "testActionType"; + mIntent.putExtra(EXTRA_ID, testId); + mIntent.putExtra(EXTRA_ACTION_TYPE, testActionType); + + mSmartActionsReceiver.onReceive(mContext, mIntent); + + verify(mMockPendingIntent).send( + eq(mContext), eq(0), isNull(), isNull(), isNull(), isNull(), any(Bundle.class)); + verify(mMockScreenshotSmartActions).notifyScreenshotAction( + mContext, testId, testActionType, true); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index aefea57155bc..8a49326add25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -61,6 +61,7 @@ import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLifetimeExtender; import com.android.systemui.statusbar.NotificationMediaManager; @@ -200,7 +201,9 @@ public class NotificationEntryManagerTest extends SysuiTestCase { () -> mNotificationRowBinder, () -> mRemoteInputManager, mLeakDetector, - mock(ForegroundServiceDismissalFeatureController.class) + mock(ForegroundServiceDismissalFeatureController.class), + mock(HeadsUpManager.class), + mock(StatusBarStateController.class) ); mEntryManager.setUpWithPresenter(mPresenter); mEntryManager.addNotificationEntryListener(mEntryListener); 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 c5374b2eb049..601df2cb4fc7 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 @@ -184,7 +184,9 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase { () -> mRowBinder, () -> mRemoteInputManager, mLeakDetector, - mock(ForegroundServiceDismissalFeatureController.class) + mock(ForegroundServiceDismissalFeatureController.class), + mock(HeadsUpManager.class), + mock(StatusBarStateController.class) ); NotifRemoteViewCache cache = new NotifRemoteViewCacheImpl(mEntryManager); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index b286f9486e13..6d411333b220 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -55,6 +55,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; +import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.EmptyShadeView; import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.NotificationLockscreenUserManager; @@ -92,6 +93,7 @@ import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.leak.LeakDetector; @@ -190,7 +192,9 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { () -> mock(NotificationRowBinder.class), () -> mRemoteInputManager, mock(LeakDetector.class), - mock(ForegroundServiceDismissalFeatureController.class) + mock(ForegroundServiceDismissalFeatureController.class), + mock(HeadsUpManager.class), + mock(StatusBarStateController.class) ); mEntryManager.setUpWithPresenter(mock(NotificationPresenter.class)); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java index debc840394b5..fa253e62ef0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java @@ -16,13 +16,18 @@ package com.android.systemui.statusbar.phone; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.res.Resources; import android.hardware.display.AmbientDisplayConfiguration; import android.os.PowerManager; +import android.provider.Settings; import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -30,6 +35,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.systemui.SysuiTestCase; import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.doze.DozeScreenState; +import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.tuner.TunerService; import org.junit.Assert; @@ -50,6 +56,7 @@ public class DozeParametersTest extends SysuiTestCase { @Mock private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy; @Mock private PowerManager mPowerManager; @Mock private TunerService mTunerService; + @Mock private BatteryController mBatteryController; @Before public void setup() { @@ -59,11 +66,12 @@ public class DozeParametersTest extends SysuiTestCase { mAmbientDisplayConfiguration, mAlwaysOnDisplayPolicy, mPowerManager, + mBatteryController, mTunerService ); } @Test - public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_false() { + public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_false() { mDozeParameters.setControlScreenOffAnimation(true); reset(mPowerManager); mDozeParameters.setControlScreenOffAnimation(false); @@ -71,7 +79,7 @@ public class DozeParametersTest extends SysuiTestCase { } @Test - public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_true() { + public void testSetControlScreenOffAnimation_setsDozeAfterScreenOff_true() { mDozeParameters.setControlScreenOffAnimation(false); reset(mPowerManager); mDozeParameters.setControlScreenOffAnimation(true); @@ -79,11 +87,28 @@ public class DozeParametersTest extends SysuiTestCase { } @Test - public void test_getWallpaperAodDuration_when_shouldControlScreenOff() { + public void testGetWallpaperAodDuration_when_shouldControlScreenOff() { mDozeParameters.setControlScreenOffAnimation(true); Assert.assertEquals( "wallpaper hides faster when controlling screen off", mDozeParameters.getWallpaperAodDuration(), DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY); } + + @Test + public void testGetAlwaysOn() { + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + + assertThat(mDozeParameters.getAlwaysOn()).isTrue(); + } + + @Test + public void testGetAlwaysOn_whenBatterySaver() { + when(mBatteryController.isAodPowerSave()).thenReturn(true); + when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true); + mDozeParameters.onTuningChanged(Settings.Secure.DOZE_ALWAYS_ON, "1"); + + assertThat(mDozeParameters.getAlwaysOn()).isFalse(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 4d6922c02c41..0c2361a9f6b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.verify; import android.app.AppOpsManager; import android.content.Intent; import android.location.LocationManager; +import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -60,8 +61,11 @@ public class LocationControllerImplTest extends SysuiTestCase { mLocationController = spy(new LocationControllerImpl(mContext, mAppOpsController, mTestableLooper.getLooper(), + new Handler(mTestableLooper.getLooper()), mock(BroadcastDispatcher.class), mock(BootCompleteCache.class))); + + mTestableLooper.processAllMessages(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java new file mode 100644 index 000000000000..8cb5f3e65a5e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java @@ -0,0 +1,130 @@ +/* + * 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.settings; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.UserHandle; +import android.util.Pair; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class FakeSettings implements SecureSettings, GlobalSettings, SystemSettings { + private final Map<SettingsKey, String> mValues = new HashMap<>(); + private final Map<SettingsKey, List<ContentObserver>> mContentObservers = + new HashMap<>(); + + public static final Uri CONTENT_URI = Uri.parse("content://settings/fake"); + + public FakeSettings() { + } + + public FakeSettings(String initialKey, String initialValue) { + putString(initialKey, initialValue); + } + + public FakeSettings(Map<String, String> initialValues) { + for (Map.Entry<String, String> kv : initialValues.entrySet()) { + putString(kv.getKey(), kv.getValue()); + } + } + + @Override + public ContentResolver getContentResolver() { + return null; + } + + @Override + public void registerContentObserverForUser(String name, ContentObserver settingsObserver, + int userHandle) { + SettingsKey key = new SettingsKey(userHandle, name); + mContentObservers.putIfAbsent(key, new ArrayList<>()); + List<ContentObserver> observers = mContentObservers.get(key); + observers.add(settingsObserver); + } + + @Override + public void unregisterContentObserver(ContentObserver settingsObserver) { + for (SettingsKey key : mContentObservers.keySet()) { + List<ContentObserver> observers = mContentObservers.get(key); + observers.remove(settingsObserver); + } + } + + @Override + public Uri getUriFor(String name) { + return Uri.withAppendedPath(CONTENT_URI, name); + } + + @Override + public int getUserId() { + return UserHandle.USER_CURRENT; + } + + @Override + public String getString(String name) { + return getStringForUser(name, getUserId()); + } + + @Override + public String getStringForUser(String name, int userHandle) { + return mValues.get(new SettingsKey(userHandle, name)); + } + + @Override + public boolean putString(String name, String value, boolean overrideableByRestore) { + return putStringForUser(name, value, null, false, getUserId(), overrideableByRestore); + } + + @Override + public boolean putString(String name, String value) { + return putString(name, value, false); + } + + @Override + public boolean putStringForUser(String name, String value, int userHandle) { + return putStringForUser(name, value, null, false, userHandle, false); + } + + @Override + public boolean putStringForUser(String name, String value, String tag, boolean makeDefault, + int userHandle, boolean overrideableByRestore) { + SettingsKey key = new SettingsKey(userHandle, name); + mValues.put(key, value); + + Uri uri = getUriFor(name); + for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) { + observer.dispatchChange(false, List.of(uri), userHandle); + } + return true; + } + + @Override + public boolean putString(String name, String value, String tag, boolean makeDefault) { + return putString(name, value); + } + + private static class SettingsKey extends Pair<Integer, String> { + SettingsKey(Integer first, String second) { + super(first, second); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java new file mode 100644 index 000000000000..0d560f237a07 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -0,0 +1,101 @@ +/* + * 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.util.settings; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.database.ContentObserver; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Collection; +import java.util.Map; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class FakeSettingsTest extends SysuiTestCase { + @Mock + ContentObserver mContentObserver; + + private FakeSettings mFakeSettings; + + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mFakeSettings = new FakeSettings(); + } + + /** + * Test FakeExecutor that receives non-delayed items to execute. + */ + @Test + public void testPutAndGet() throws Settings.SettingNotFoundException { + mFakeSettings.putInt("foobar", 1); + assertThat(mFakeSettings.getInt("foobar")).isEqualTo(1); + } + + @Test + public void testInitialize() { + mFakeSettings = new FakeSettings("abra", "cadabra"); + assertThat(mFakeSettings.getString("abra")).isEqualTo("cadabra"); + } + + @Test + public void testInitializeWithMap() { + mFakeSettings = new FakeSettings(Map.of("one fish", "two fish", "red fish", "blue fish")); + assertThat(mFakeSettings.getString("red fish")).isEqualTo("blue fish"); + assertThat(mFakeSettings.getString("one fish")).isEqualTo("two fish"); + } + + @Test + public void testRegisterContentObserver() { + mFakeSettings.registerContentObserver("cat", mContentObserver); + + mFakeSettings.putString("cat", "hat"); + + verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt()); + } + + @Test + public void testUnregisterContentObserver() { + mFakeSettings.registerContentObserver("cat", mContentObserver); + mFakeSettings.unregisterContentObserver(mContentObserver); + + mFakeSettings.putString("cat", "hat"); + + verify(mContentObserver, never()).dispatchChange( + anyBoolean(), any(Collection.class), anyInt()); + } +} diff --git a/services/Android.bp b/services/Android.bp index 40b925de95d6..f0144ac1c695 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -154,10 +154,14 @@ droidstubs { java_library { name: "android_system_server_stubs_current", + defaults: ["android_stubs_dists_default"], srcs: [":services-stubs.sources"], installable: false, static_libs: ["android_module_lib_stubs_current"], sdk_version: "none", system_modules: "none", java_version: "1.8", + dist: { + dir: "apistubs/android/system-server", + }, } diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java index 7f6dc14f3793..c87dcd7874f8 100644 --- a/services/core/java/com/android/server/GestureLauncherService.java +++ b/services/core/java/com/android/server/GestureLauncherService.java @@ -75,6 +75,16 @@ public class GestureLauncherService extends SystemService { */ @VisibleForTesting static final long POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS = 500; + /** + * Number of taps required to launch panic ui. + */ + private static final int PANIC_POWER_TAP_COUNT_THRESHOLD = 5; + + /** + * Number of taps required to launch camera shortcut. + */ + private static final int CAMERA_POWER_TAP_COUNT_THRESHOLD = 2; + /** The listener that receives the gesture event. */ private final GestureEventListener mGestureListener = new GestureEventListener(); private final CameraLiftTriggerEventListener mCameraLiftTriggerListener = @@ -135,6 +145,7 @@ public class GestureLauncherService extends SystemService { private long mLastPowerDown; private int mPowerButtonConsecutiveTaps; + private int mPowerButtonSlowConsecutiveTaps; public GestureLauncherService(Context context) { this(context, new MetricsLogger()); @@ -350,9 +361,8 @@ public class GestureLauncherService extends SystemService { * Whether to enable panic button gesture. */ public static boolean isPanicButtonGestureEnabled(Context context, int userId) { - return isCameraLaunchEnabled(context.getResources()) - && (Settings.Secure.getIntForUser(context.getContentResolver(), - Settings.Secure.PANIC_GESTURE_ENABLED, 0, userId) != 0); + return Settings.Secure.getIntForUser(context.getContentResolver(), + Settings.Secure.PANIC_GESTURE_ENABLED, 0, userId) != 0; } /** @@ -384,6 +394,13 @@ public class GestureLauncherService extends SystemService { isCameraLiftTriggerEnabled(resources); } + /** + * Attempts to intercept power key down event by detecting certain gesture patterns + * + * @param interactive true if the event's policy contains {@code FLAG_INTERACTIVE} + * @param outLaunched true if some action is taken as part of the key intercept (eg, app launch) + * @return true if the key down event is intercepted + */ public boolean interceptPowerKeyDown(KeyEvent event, boolean interactive, MutableBoolean outLaunched) { if (event.isLongPress()) { @@ -392,41 +409,60 @@ public class GestureLauncherService extends SystemService { // taps or consecutive taps, so we want to ignore the long press event. return false; } - boolean launched = false; + boolean launchCamera = false; + boolean launchPanic = false; boolean intercept = false; long powerTapInterval; synchronized (this) { powerTapInterval = event.getEventTime() - mLastPowerDown; - if (mCameraDoubleTapPowerEnabled - && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) { - launched = true; - intercept = interactive; - mPowerButtonConsecutiveTaps++; - } else if (powerTapInterval < POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) { - mPowerButtonConsecutiveTaps++; - } else { + mLastPowerDown = event.getEventTime(); + if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) { + // Tap too slow, reset consecutive tap counts. + mPowerButtonConsecutiveTaps = 1; + mPowerButtonSlowConsecutiveTaps = 1; + } else if (powerTapInterval >= CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS) { + // Tap too slow for shortcuts mPowerButtonConsecutiveTaps = 1; + mPowerButtonSlowConsecutiveTaps++; + } else { + // Fast consecutive tap + mPowerButtonConsecutiveTaps++; + mPowerButtonSlowConsecutiveTaps++; + } + if (mPanicButtonGestureEnabled + && mPowerButtonConsecutiveTaps == PANIC_POWER_TAP_COUNT_THRESHOLD) { + launchPanic = true; + intercept = interactive; + } else if (mCameraDoubleTapPowerEnabled + && powerTapInterval < CAMERA_POWER_DOUBLE_TAP_MAX_TIME_MS + && mPowerButtonConsecutiveTaps == CAMERA_POWER_TAP_COUNT_THRESHOLD) { + launchCamera = true; + intercept = interactive; } - mLastPowerDown = event.getEventTime(); } - if (DBG && mPowerButtonConsecutiveTaps > 1) { - Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps) + - " consecutive power button taps detected"); + if (mPowerButtonConsecutiveTaps > 1 || mPowerButtonSlowConsecutiveTaps > 1) { + Slog.i(TAG, Long.valueOf(mPowerButtonConsecutiveTaps) + + " consecutive power button taps detected, " + + Long.valueOf(mPowerButtonSlowConsecutiveTaps) + + " consecutive slow power button taps detected"); } - if (launched) { + if (launchCamera) { Slog.i(TAG, "Power button double tap gesture detected, launching camera. Interval=" + powerTapInterval + "ms"); - launched = handleCameraGesture(false /* useWakelock */, + launchCamera = handleCameraGesture(false /* useWakelock */, StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP); - if (launched) { + if (launchCamera) { mMetricsLogger.action(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE, (int) powerTapInterval); } + } else if (launchPanic) { + Slog.i(TAG, "Panic gesture detected, launching panic."); } - mMetricsLogger.histogram("power_consecutive_short_tap_count", mPowerButtonConsecutiveTaps); + mMetricsLogger.histogram("power_consecutive_short_tap_count", + mPowerButtonSlowConsecutiveTaps); mMetricsLogger.histogram("power_double_tap_interval", (int) powerTapInterval); - outLaunched.value = launched; - return intercept && launched; + outLaunched.value = launchCamera || launchPanic; + return intercept && (launchCamera || launchPanic); } /** diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 816d663e09de..1520dd351c97 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3238,6 +3238,12 @@ class StorageManagerService extends IStorageManager.Stub @Override public void lockUserKey(int userId) { + // Do not lock user 0 data for headless system user + if (userId == UserHandle.USER_SYSTEM + && UserManager.isHeadlessSystemUserMode()) { + throw new IllegalArgumentException("Headless system user data cannot be locked.."); + } + enforcePermission(android.Manifest.permission.STORAGE_INTERNAL); try { diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index be080e5cce62..915189c085c2 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -81,6 +81,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static android.app.UiModeManager.DEFAULT_PRIORITY; import static android.app.UiModeManager.MODE_NIGHT_AUTO; import static android.app.UiModeManager.MODE_NIGHT_CUSTOM; import static android.app.UiModeManager.MODE_NIGHT_YES; @@ -1446,6 +1447,8 @@ final class UiModeManagerService extends SystemService { pw.println(" Print this help text."); pw.println(" night [yes|no|auto|custom]"); pw.println(" Set or read night mode."); + pw.println(" car [yes|no]"); + pw.println(" Set or read car mode."); pw.println(" time [start|end] <ISO time>"); pw.println(" Set custom start/end schedule time" + " (night mode must be set to custom to apply)."); @@ -1461,6 +1464,8 @@ final class UiModeManagerService extends SystemService { switch (cmd) { case "night": return handleNightMode(); + case "car": + return handleCarMode(); case "time": return handleCustomTime(); default: @@ -1558,6 +1563,34 @@ final class UiModeManagerService extends SystemService { return -1; } } + + private int handleCarMode() throws RemoteException { + final PrintWriter err = getErrPrintWriter(); + final String modeStr = getNextArg(); + if (modeStr == null) { + printCurrentCarMode(); + return 0; + } + + if (modeStr.equals("yes")) { + mInterface.enableCarMode(0 /* flags */, DEFAULT_PRIORITY, "" /* package */); + printCurrentCarMode(); + return 0; + } else if (modeStr.equals("no")) { + mInterface.disableCarMode(0 /* flags */); + printCurrentCarMode(); + return 0; + } else { + err.println("Error: mode must be 'yes', or 'no'"); + return -1; + } + } + + private void printCurrentCarMode() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + final int currMode = mInterface.getCurrentModeType(); + pw.println("Car mode: " + (currMode == Configuration.UI_MODE_TYPE_CAR ? "yes" : "no")); + } } public final class LocalService extends UiModeManagerInternal { diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index ae89f37fa80b..bd51c7a1773d 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -690,7 +690,7 @@ public final class ActiveServices { } if (allowBackgroundActivityStarts) { - r.whitelistBgActivityStartsOnServiceStart(); + r.allowBgActivityStartsOnServiceStart(); } ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); @@ -2045,7 +2045,7 @@ public final class ActiveServices { s.whitelistManager = true; } if ((flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) { - s.setHasBindingWhitelistingBgActivityStarts(true); + s.setAllowedBgActivityStartsByBinding(true); } if (s.app != null) { updateServiceClientActivitiesLocked(s.app, c, true); @@ -3457,9 +3457,9 @@ public final class ActiveServices { updateWhitelistManagerLocked(s.app); } } - // And do the same for bg activity starts whitelisting. + // And do the same for bg activity starts ability. if ((c.flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) { - s.updateHasBindingWhitelistingBgActivityStarts(); + s.updateIsAllowedBgActivityStartsByBinding(); } if (s.app != null) { updateServiceClientActivitiesLocked(s.app, c, true); diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index 09ed16ef793b..775119c18037 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -253,7 +253,8 @@ final class ActivityManagerConstants extends ContentObserver { // allowing the next pending start to run. public long BG_START_TIMEOUT = DEFAULT_BG_START_TIMEOUT; - // For how long after a whitelisted service's start its process can start a background activity + // For a service that has been allowed to start background activities, how long after it started + // its process can start a background activity. public long SERVICE_BG_ACTIVITY_START_TIMEOUT = DEFAULT_SERVICE_BG_ACTIVITY_START_TIMEOUT; // Initial backoff delay for retrying bound foreground services diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 57250d52b2ab..cfd2bf913b9c 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -291,6 +291,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.EventLog; +import android.util.IntArray; import android.util.Log; import android.util.Pair; import android.util.PrintWriterPrinter; @@ -310,6 +311,7 @@ import android.view.autofill.AutofillManagerInternal; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.app.IAppOpsActiveCallback; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.app.ProcessMap; @@ -657,6 +659,14 @@ public class ActivityManagerService extends IActivityManager.Stub int mUidChangeDispatchCount; /** + * Uids of apps with current active camera sessions. Access synchronized on + * the IntArray instance itself, and no other locks must be acquired while that + * one is held. + */ + @GuardedBy("mActiveCameraUids") + final IntArray mActiveCameraUids = new IntArray(4); + + /** * Helper class which strips out priority and proto arguments then calls the dump function with * the appropriate arguments. If priority arguments are omitted, function calls the legacy * dump command. @@ -2053,7 +2063,10 @@ public class ActivityManagerService extends IActivityManager.Stub } if (proc != null) { long startTime = SystemClock.currentThreadTimeMillis(); - long pss = Debug.getPss(pid, tmp, null); + // skip background PSS calculation of apps that are capturing + // camera imagery + final boolean usingCamera = isCameraActiveForUid(proc.uid); + long pss = usingCamera ? 0 : Debug.getPss(pid, tmp, null); long endTime = SystemClock.currentThreadTimeMillis(); synchronized (ActivityManagerService.this) { if (pss != 0 && proc.thread != null && proc.setProcState == procState @@ -2066,6 +2079,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.abortNextPssTime(proc.procStateMemTracker); if (DEBUG_PSS) Slog.d(TAG_PSS, "Skipped pss collection of " + pid + ": " + (proc.thread == null ? "NO_THREAD " : "") + + (usingCamera ? "CAMERA " : "") + (proc.pid != pid ? "PID_CHANGED " : "") + " initState=" + procState + " curState=" + proc.setProcState + " " + @@ -2155,6 +2169,14 @@ public class ActivityManagerService extends IActivityManager.Stub } } }); + + final int[] cameraOp = {AppOpsManager.OP_CAMERA}; + mAppOpsService.startWatchingActive(cameraOp, new IAppOpsActiveCallback.Stub() { + @Override + public void opActiveChanged(int op, int uid, String packageName, boolean active) { + cameraActiveChanged(uid, active); + } + }); } public void setWindowManager(WindowManagerService wm) { @@ -18266,6 +18288,27 @@ public class ActivityManagerService extends IActivityManager.Stub } } + final void cameraActiveChanged(@UserIdInt int uid, boolean active) { + synchronized (mActiveCameraUids) { + final int curIndex = mActiveCameraUids.indexOf(uid); + if (active) { + if (curIndex < 0) { + mActiveCameraUids.add(uid); + } + } else { + if (curIndex >= 0) { + mActiveCameraUids.remove(curIndex); + } + } + } + } + + final boolean isCameraActiveForUid(@UserIdInt int uid) { + synchronized (mActiveCameraUids) { + return mActiveCameraUids.indexOf(uid) >= 0; + } + } + @GuardedBy("this") final void doStopUidLocked(int uid, final UidRecord uidRec) { mServices.stopInBackgroundLocked(uid); @@ -20518,4 +20561,17 @@ public class ActivityManagerService extends IActivityManager.Stub Binder.restoreCallingIdentity(token); } } + + /** + * Resets the state of the {@link com.android.server.am.AppErrors} instance. + * This is intended for testing within the CTS only and is protected by + * android.permission.RESET_APP_ERRORS. + */ + @Override + public void resetAppErrors() { + enforceCallingPermission(Manifest.permission.RESET_APP_ERRORS, "resetAppErrors"); + synchronized (this) { + mAppErrors.resetStateLocked(); + } + } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index a36a18b4cf5c..2e92ac0fb3d6 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -107,6 +107,16 @@ class AppErrors { mPackageWatchdog = watchdog; } + /** Resets the current state but leaves the constructor-provided fields unchanged. */ + public void resetStateLocked() { + Slog.i(TAG, "Resetting AppErrors"); + mAppsNotReportingCrashes.clear(); + mProcessCrashTimes.clear(); + mProcessCrashTimesPersistent.clear(); + mProcessCrashShowDialogTimes.clear(); + mBadProcesses.clear(); + } + void dumpDebug(ProtoOutputStream proto, long fieldId, String dumpPackage) { if (mProcessCrashTimes.getMap().isEmpty() && mBadProcesses.getMap().isEmpty()) { return; diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index be17b1bc600c..494f06ebc324 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -62,7 +62,8 @@ public class BroadcastConstants { public float DEFERRAL_DECAY_FACTOR = DEFAULT_DEFERRAL_DECAY_FACTOR; // Minimum that the deferral time can decay to until the backlog fully clears public long DEFERRAL_FLOOR = DEFAULT_DEFERRAL_FLOOR; - // For how long after a whitelisted receiver's start its process can start a background activity + // For a receiver that has been allowed to start background activities, how long after it + // started its process can start a background activity. public long ALLOW_BG_ACTIVITY_START_TIMEOUT = DEFAULT_ALLOW_BG_ACTIVITY_START_TIMEOUT; // Settings override tracking for this instance diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 8ef67f97e8d4..40743b8be1ea 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -89,8 +89,8 @@ final class BroadcastRecord extends Binder { int manifestSkipCount; // number of manifest receivers skipped. BroadcastQueue queue; // the outbound queue handling this broadcast - // if set to true, app's process will be temporarily whitelisted to start activities - // from background for the duration of the broadcast dispatch + // if set to true, app's process will be temporarily allowed to start activities from background + // for the duration of the broadcast dispatch final boolean allowBackgroundActivityStarts; static final int IDLE = 0; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b6ad1a526165..1038069f5d11 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2050,7 +2050,9 @@ public final class ProcessList { final int pid = precedence.pid; long now = System.currentTimeMillis(); final long end = now + PROC_KILL_TIMEOUT; + final int oldPolicy = StrictMode.getThreadPolicyMask(); try { + StrictMode.setThreadPolicyMask(0); Process.waitForProcessDeath(pid, PROC_KILL_TIMEOUT); // It's killed successfully, but we'd make sure the cleanup work is done. synchronized (precedence) { @@ -2069,9 +2071,11 @@ public final class ProcessList { } } } catch (Exception e) { - // It's still alive... + // It's still alive... maybe blocked at uninterruptible sleep ? Slog.wtf(TAG, precedence.toString() + " refused to die, but we need to launch " - + app); + + app, e); + } finally { + StrictMode.setThreadPolicyMask(oldPolicy); } } try { @@ -2416,7 +2420,15 @@ public final class ProcessList { ProcessList.killProcessGroup(app.uid, app.pid); checkSlow(startTime, "startProcess: done killing old proc"); - Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + if (!app.killed || mService.mLastMemoryLevel <= ProcessStats.ADJ_MEM_FACTOR_NORMAL + || app.setProcState < ActivityManager.PROCESS_STATE_CACHED_EMPTY + || app.lastCachedPss < getCachedRestoreThresholdKb()) { + // Throw a wtf if it's not killed, or killed but not because the system was in + // memory pressure + the app was in "cch-empty" and used large amount of memory + Slog.wtf(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } else { + Slog.w(TAG_PROCESSES, app.toString() + " is attached to a previous process"); + } // We are not going to re-use the ProcessRecord, as we haven't dealt with the cleanup // routine of it yet, but we'd set it as the precedence of the new process. precedence = app; @@ -2819,7 +2831,15 @@ public final class ProcessList { // We are re-adding a persistent process. Whatevs! Just leave it there. Slog.w(TAG, "Re-adding persistent process " + proc); } else if (old != null) { - Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); + if (old.killed) { + // The old process has been killed, we probably haven't had + // a chance to clean up the old record, just log a warning + Slog.w(TAG, "Existing proc " + old + " was killed " + + (SystemClock.uptimeMillis() - old.mKillTime) + + "ms ago when adding " + proc); + } else { + Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc); + } } UidRecord uidRec = mActiveUids.get(proc.uid); if (uidRec == null) { diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index c5152c081e70..6e1bd8faeaf9 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -274,7 +274,7 @@ class ProcessRecord implements WindowProcessListener { final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>(); // All ContentProviderRecord process is using final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>(); - // A set of tokens that currently contribute to this process being temporarily whitelisted + // A set of tokens that currently contribute to this process being temporarily allowed // to start activities even if it's not in the foreground final ArraySet<Binder> mAllowBackgroundActivityStartsTokens = new ArraySet<>(); // a set of UIDs of all bound clients @@ -352,6 +352,8 @@ class ProcessRecord implements WindowProcessListener { boolean mReachable; // Whether or not this process is reachable from given process + long mKillTime; // The timestamp in uptime when this process was killed. + void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startTime) { this.startUid = startUid; @@ -626,7 +628,7 @@ class ProcessRecord implements WindowProcessListener { } } if (mAllowBackgroundActivityStartsTokens.size() > 0) { - pw.print(prefix); pw.println("Background activity start whitelist tokens:"); + pw.print(prefix); pw.println("Background activity start tokens:"); for (int i = 0; i < mAllowBackgroundActivityStartsTokens.size(); i++) { pw.print(prefix); pw.print(" - "); pw.println(mAllowBackgroundActivityStartsTokens.valueAt(i)); @@ -925,6 +927,7 @@ class ProcessRecord implements WindowProcessListener { if (!mPersistent) { killed = true; killedByAm = true; + mKillTime = SystemClock.uptimeMillis(); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 828ac71eccfe..db05d65b92fe 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -131,13 +131,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN int pendingConnectionImportance; // To be filled in to ProcessRecord once it connects // any current binding to this service has BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS flag? - private boolean mHasBindingWhitelistingBgActivityStarts; - // is this service currently whitelisted to start activities from background by providing + private boolean mIsAllowedBgActivityStartsByBinding; + // is this service currently allowed to start activities from background by providing // allowBackgroundActivityStarts=true to startServiceLocked()? - private boolean mHasStartedWhitelistingBgActivityStarts; - // used to clean up the state of hasStartedWhitelistingBgActivityStarts after a timeout - private Runnable mStartedWhitelistingBgActivityStartsCleanUp; - private ProcessRecord mAppForStartedWhitelistingBgActivityStarts; + private boolean mIsAllowedBgActivityStartsByStart; + // used to clean up the state of mIsAllowedBgActivityStartsByStart after a timeout + private Runnable mCleanUpAllowBgActivityStartsByStartCallback; + private ProcessRecord mAppForAllowingBgActivityStartsByStart; // allow while-in-use permissions in foreground service or not. // while-in-use permissions in FGS started from background might be restricted. @@ -396,13 +396,13 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN if (whitelistManager) { pw.print(prefix); pw.print("whitelistManager="); pw.println(whitelistManager); } - if (mHasBindingWhitelistingBgActivityStarts) { - pw.print(prefix); pw.print("hasBindingWhitelistingBgActivityStarts="); - pw.println(mHasBindingWhitelistingBgActivityStarts); + if (mIsAllowedBgActivityStartsByBinding) { + pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByBinding="); + pw.println(mIsAllowedBgActivityStartsByBinding); } - if (mHasStartedWhitelistingBgActivityStarts) { - pw.print(prefix); pw.print("hasStartedWhitelistingBgActivityStarts="); - pw.println(mHasStartedWhitelistingBgActivityStarts); + if (mIsAllowedBgActivityStartsByStart) { + pw.print(prefix); pw.print("mIsAllowedBgActivityStartsByStart="); + pw.println(mIsAllowedBgActivityStartsByStart); } pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs="); pw.println(mAllowWhileInUsePermissionInFgs); @@ -560,31 +560,31 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN public void setProcess(ProcessRecord _proc) { if (_proc != null) { - // We're starting a new process for this service, but a previous one is whitelisted. - // Remove that whitelisting now (unless the new process is the same as the previous one, - // which is a common case). - if (mAppForStartedWhitelistingBgActivityStarts != null) { - if (mAppForStartedWhitelistingBgActivityStarts != _proc) { - mAppForStartedWhitelistingBgActivityStarts + // We're starting a new process for this service, but a previous one is allowed to start + // background activities. Remove that ability now (unless the new process is the same as + // the previous one, which is a common case). + if (mAppForAllowingBgActivityStartsByStart != null) { + if (mAppForAllowingBgActivityStartsByStart != _proc) { + mAppForAllowingBgActivityStartsByStart .removeAllowBackgroundActivityStartsToken(this); - ams.mHandler.removeCallbacks(mStartedWhitelistingBgActivityStartsCleanUp); + ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback); } } // Make sure the cleanup callback knows about the new process. - mAppForStartedWhitelistingBgActivityStarts = mHasStartedWhitelistingBgActivityStarts + mAppForAllowingBgActivityStartsByStart = mIsAllowedBgActivityStartsByStart ? _proc : null; - if (mHasStartedWhitelistingBgActivityStarts - || mHasBindingWhitelistingBgActivityStarts) { + if (mIsAllowedBgActivityStartsByStart + || mIsAllowedBgActivityStartsByBinding) { _proc.addAllowBackgroundActivityStartsToken(this); } else { _proc.removeAllowBackgroundActivityStartsToken(this); } } if (app != null && app != _proc) { - // If the old app is whitelisted because of a service start, leave it whitelisted until - // the cleanup callback runs. Otherwise we can remove it from the whitelist immediately - // (it can't be bound now). - if (!mHasStartedWhitelistingBgActivityStarts) { + // If the old app is allowed to start bg activities because of a service start, leave it + // that way until the cleanup callback runs. Otherwise we can remove its bg activity + // start ability immediately (it can't be bound now). + if (!mIsAllowedBgActivityStartsByStart) { app.removeAllowBackgroundActivityStartsToken(this); } app.updateBoundClientUids(); @@ -648,89 +648,88 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return startRequested && (stopIfKilled || isStartCanceled) && pendingStarts.isEmpty(); } - void updateHasBindingWhitelistingBgActivityStarts() { - boolean hasWhitelistingBinding = false; + void updateIsAllowedBgActivityStartsByBinding() { + boolean isAllowedByBinding = false; for (int conni = connections.size() - 1; conni >= 0; conni--) { ArrayList<ConnectionRecord> cr = connections.valueAt(conni); for (int i = 0; i < cr.size(); i++) { if ((cr.get(i).flags & Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS) != 0) { - hasWhitelistingBinding = true; + isAllowedByBinding = true; break; } } - if (hasWhitelistingBinding) { + if (isAllowedByBinding) { break; } } - setHasBindingWhitelistingBgActivityStarts(hasWhitelistingBinding); + setAllowedBgActivityStartsByBinding(isAllowedByBinding); } - void setHasBindingWhitelistingBgActivityStarts(boolean newValue) { - if (mHasBindingWhitelistingBgActivityStarts != newValue) { - mHasBindingWhitelistingBgActivityStarts = newValue; - updateParentProcessBgActivityStartsWhitelistingToken(); + void setAllowedBgActivityStartsByBinding(boolean newValue) { + if (mIsAllowedBgActivityStartsByBinding != newValue) { + mIsAllowedBgActivityStartsByBinding = newValue; + updateParentProcessBgActivityStartsToken(); } } /** - * Called when the service is started with allowBackgroundActivityStarts set. We whitelist - * it for background activity starts, setting up a callback to remove the whitelisting after a - * timeout. Note that the whitelisting persists for the process even if the service is - * subsequently stopped. + * Called when the service is started with allowBackgroundActivityStarts set. We allow + * it for background activity starts, setting up a callback to remove this ability after a + * timeout. Note that the ability for starting background activities persists for the process + * even if the service is subsequently stopped. */ - void whitelistBgActivityStartsOnServiceStart() { - setHasStartedWhitelistingBgActivityStarts(true); + void allowBgActivityStartsOnServiceStart() { + setAllowedBgActivityStartsByStart(true); if (app != null) { - mAppForStartedWhitelistingBgActivityStarts = app; + mAppForAllowingBgActivityStartsByStart = app; } // This callback is stateless, so we create it once when we first need it. - if (mStartedWhitelistingBgActivityStartsCleanUp == null) { - mStartedWhitelistingBgActivityStartsCleanUp = () -> { + if (mCleanUpAllowBgActivityStartsByStartCallback == null) { + mCleanUpAllowBgActivityStartsByStartCallback = () -> { synchronized (ams) { - if (app == mAppForStartedWhitelistingBgActivityStarts) { - // The process we whitelisted is still running the service. We remove - // the started whitelisting, but it may still be whitelisted via bound - // connections. - setHasStartedWhitelistingBgActivityStarts(false); - } else if (mAppForStartedWhitelistingBgActivityStarts != null) { - // The process we whitelisted is not running the service. It therefore - // can't be bound so we can unconditionally remove the whitelist. - mAppForStartedWhitelistingBgActivityStarts + if (app == mAppForAllowingBgActivityStartsByStart) { + // The process we allowed is still running the service. We remove + // the ability by start, but it may still be allowed via bound connections. + setAllowedBgActivityStartsByStart(false); + } else if (mAppForAllowingBgActivityStartsByStart != null) { + // The process we allowed is not running the service. It therefore can't be + // bound so we can unconditionally remove the ability. + mAppForAllowingBgActivityStartsByStart .removeAllowBackgroundActivityStartsToken(ServiceRecord.this); } - mAppForStartedWhitelistingBgActivityStarts = null; + mAppForAllowingBgActivityStartsByStart = null; } }; } // if there's a request pending from the past, drop it before scheduling a new one - ams.mHandler.removeCallbacks(mStartedWhitelistingBgActivityStartsCleanUp); - ams.mHandler.postDelayed(mStartedWhitelistingBgActivityStartsCleanUp, + ams.mHandler.removeCallbacks(mCleanUpAllowBgActivityStartsByStartCallback); + ams.mHandler.postDelayed(mCleanUpAllowBgActivityStartsByStartCallback, ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT); } - private void setHasStartedWhitelistingBgActivityStarts(boolean newValue) { - if (mHasStartedWhitelistingBgActivityStarts != newValue) { - mHasStartedWhitelistingBgActivityStarts = newValue; - updateParentProcessBgActivityStartsWhitelistingToken(); + private void setAllowedBgActivityStartsByStart(boolean newValue) { + if (mIsAllowedBgActivityStartsByStart != newValue) { + mIsAllowedBgActivityStartsByStart = newValue; + updateParentProcessBgActivityStartsToken(); } } /** - * Whether the process this service runs in should be temporarily whitelisted to start + * Whether the process this service runs in should be temporarily allowed to start * activities from background depends on the current state of both - * {@code hasStartedWhitelistingBgActivityStarts} and - * {@code hasBindingWhitelistingBgActivityStarts}. If either is true, this ServiceRecord + * {@code mIsAllowedBgActivityStartsByStart} and + * {@code mIsAllowedBgActivityStartsByBinding}. If either is true, this ServiceRecord * should be contributing as a token in parent ProcessRecord. * * @see com.android.server.am.ProcessRecord#mAllowBackgroundActivityStartsTokens */ - private void updateParentProcessBgActivityStartsWhitelistingToken() { + private void updateParentProcessBgActivityStartsToken() { if (app == null) { return; } - if (mHasStartedWhitelistingBgActivityStarts || mHasBindingWhitelistingBgActivityStarts) { + if (mIsAllowedBgActivityStartsByStart || mIsAllowedBgActivityStartsByBinding) { // if the token is already there it's safe to "re-add it" - we're dealing with // a set of Binder objects app.addAllowBackgroundActivityStartsToken(this); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 433317b038b3..e6480fc6cde8 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2059,6 +2059,8 @@ public class AppOpsService extends IAppOpsService.Stub { public void getHistoricalOps(int uid, String packageName, String attributionTag, List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis, int flags, RemoteCallback callback) { + PackageManager pm = mContext.getPackageManager(); + ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter, beginTimeMillis, endTimeMillis, flags); Objects.requireNonNull(callback, "callback cannot be null"); @@ -2066,8 +2068,16 @@ public class AppOpsService extends IAppOpsService.Stub { ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class); boolean isCallerInstrumented = ami.isUidCurrentlyInstrumented(Binder.getCallingUid()); boolean isCallerSystem = Binder.getCallingPid() == Process.myPid(); + boolean isCallerPermissionController; + try { + isCallerPermissionController = pm.getPackageUid( + mContext.getPackageManager().getPermissionControllerPackageName(), 0) + == Binder.getCallingUid(); + } catch (PackageManager.NameNotFoundException doesNotHappen) { + return; + } - if (!isCallerSystem && !isCallerInstrumented) { + if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController) { mHandler.post(() -> callback.sendResult(new Bundle())); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 3ff6ec1afa41..86e6a3220507 100755 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -244,7 +244,7 @@ abstract class HdmiCecLocalDevice { if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { return false; } - // Cache incoming message. Note that it caches only white-listed one. + // Cache incoming message if it is included in the list of cacheable opcodes. mCecMessageCache.cacheMessage(message); return onMessage(message); } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index aed94fc85431..64d70d6601f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -225,7 +225,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { if (SystemProperties.getBoolean(Constants.PROPERTY_KEEP_AWAKE, true)) { mWakeLock = new SystemWakeLock(); } else { - // Create a dummy lock object that doesn't do anything about wake lock, + // Create a stub lock object that doesn't do anything about wake lock, // hence allows the device to go to sleep even if it's the active source. mWakeLock = new ActiveWakeLock() { @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 2c0ddaf35182..804cc92cca08 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -1667,6 +1667,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (avr == null) { return; } + setArcStatus(false); // Seq #44. removeAction(RequestArcInitiationAction.class); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 0154fe07a418..254285dfbd41 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2942,7 +2942,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub vis = 0; } if (!mCurPerceptible) { - vis = 0; + vis &= ~InputMethodService.IME_VISIBLE; } // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index f69c8239762d..d933c109b27d 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -17,6 +17,9 @@ package com.android.server.location; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.app.AppOpsManager.OP_MOCK_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -120,10 +123,16 @@ import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationAttributionHelper; +import com.android.server.location.util.LocationPermissionsHelper; +import com.android.server.location.util.LocationPowerSaveModeHelper; import com.android.server.location.util.LocationUsageLogger; +import com.android.server.location.util.ScreenInteractiveHelper; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.SystemAppForegroundHelper; import com.android.server.location.util.SystemAppOpsHelper; +import com.android.server.location.util.SystemLocationPermissionsHelper; +import com.android.server.location.util.SystemLocationPowerSaveModeHelper; +import com.android.server.location.util.SystemScreenInteractiveHelper; import com.android.server.location.util.SystemSettingsHelper; import com.android.server.location.util.SystemUserInfoHelper; import com.android.server.location.util.UserInfoHelper; @@ -173,7 +182,7 @@ public class LocationManagerService extends ILocationManager.Stub { publishBinderService(Context.LOCATION_SERVICE, mService); // client caching behavior is only enabled after seeing the first invalidate - invalidateLocalLocationEnabledCaches(); + LocationManager.invalidateLocalLocationEnabledCaches(); // disable caching for our own process Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class)) .disableLocalLocationEnabledCaches(); @@ -486,7 +495,7 @@ public class LocationManagerService extends ILocationManager.Stub { private void onLocationModeChanged(int userId) { boolean enabled = mSettingsHelper.isLocationEnabled(userId); - invalidateLocalLocationEnabledCaches(); + LocationManager.invalidateLocalLocationEnabledCaches(); if (D) { Log.d(TAG, "[u" + userId + "] location enabled = " + enabled); @@ -1232,19 +1241,20 @@ public class LocationManagerService extends ILocationManager.Stub { if (!currentlyMonitoring) { if (allowMonitoring) { if (!highPower) { - return mAppOpsHelper.startLocationMonitoring(mCallerIdentity); + return mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, mCallerIdentity); } else { - return mAppOpsHelper.startHighPowerLocationMonitoring(mCallerIdentity); + return mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + mCallerIdentity); } } } else { - if (!allowMonitoring || !mAppOpsHelper.checkLocationAccess(mCallerIdentity, + if (!allowMonitoring || !mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp( LocationPermissions.getPermissionLevel(mContext, mCallerIdentity.getUid(), - mCallerIdentity.getPid()))) { + mCallerIdentity.getPid())), mCallerIdentity)) { if (!highPower) { - mAppOpsHelper.stopLocationMonitoring(mCallerIdentity); + mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, mCallerIdentity); } else { - mAppOpsHelper.stopHighPowerLocationMonitoring(mCallerIdentity); + mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, mCallerIdentity); } return false; } @@ -1589,8 +1599,9 @@ public class LocationManagerService extends ILocationManager.Stub { continue; } - if (!mAppOpsHelper.checkLocationAccess(identity, - record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE)) { + if (!mAppOpsHelper.checkOpNoThrow(LocationPermissions.asAppOp( + record.mRequest.isCoarse() ? PERMISSION_COARSE : PERMISSION_FINE), + identity)) { continue; } final boolean isBatterySaverDisablingLocation = shouldThrottleRequests @@ -2118,7 +2129,8 @@ public class LocationManagerService extends ILocationManager.Stub { } // appops check should always be right before delivery - if (!mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) { + if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + identity)) { return null; } @@ -2179,7 +2191,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (locationAgeMs < MAX_CURRENT_LOCATION_AGE_MS) { // appops check should always be right before delivery - if (mAppOpsHelper.noteLocationAccess(identity, permissionLevel)) { + if (mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + identity)) { transport.deliverResult(lastLocation); } else { transport.deliverResult(null); @@ -2329,7 +2342,7 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public boolean sendExtraCommand(String provider, String command, Bundle extras) { + public void sendExtraCommand(String provider, String command, Bundle extras) { LocationPermissions.enforceCallingOrSelfLocationPermission(mContext, PERMISSION_COARSE); mContext.enforceCallingOrSelfPermission( permission.ACCESS_LOCATION_EXTRA_COMMANDS, null); @@ -2350,8 +2363,6 @@ public class LocationManagerService extends ILocationManager.Stub { LocationStatsEnums.USAGE_ENDED, LocationStatsEnums.API_SEND_EXTRA_COMMAND, provider); - - return true; } @Override @@ -2553,7 +2564,8 @@ public class LocationManagerService extends ILocationManager.Stub { r.mLastFixBroadcast = location; // appops check should always be right before delivery - if (!mAppOpsHelper.noteLocationAccess(receiver.mCallerIdentity, permissionLevel)) { + if (!mAppOpsHelper.noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), + receiver.mCallerIdentity)) { continue; } @@ -2644,7 +2656,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2664,7 +2676,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2687,7 +2699,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2708,7 +2720,7 @@ public class LocationManagerService extends ILocationManager.Stub { // unsafe is ok because app ops will verify the package name CallerIdentity identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag); - if (!mAppOpsHelper.noteMockLocationAccess(identity)) { + if (!mAppOpsHelper.noteOp(OP_MOCK_LOCATION, identity)) { return; } @@ -2926,24 +2938,36 @@ public class LocationManagerService extends ILocationManager.Stub { private final UserInfoHelper mUserInfoHelper; private final SystemAppOpsHelper mAppOpsHelper; + private final SystemLocationPermissionsHelper mLocationPermissionsHelper; private final SystemSettingsHelper mSettingsHelper; private final SystemAppForegroundHelper mAppForegroundHelper; - private final LocationUsageLogger mLocationUsageLogger; + private final SystemLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; + private final SystemScreenInteractiveHelper mScreenInteractiveHelper; private final LocationAttributionHelper mLocationAttributionHelper; + private final LocationUsageLogger mLocationUsageLogger; + private final LocationRequestStatistics mLocationRequestStatistics; SystemInjector(Context context, UserInfoHelper userInfoHelper) { mUserInfoHelper = userInfoHelper; mAppOpsHelper = new SystemAppOpsHelper(context); + mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context, + mAppOpsHelper); mSettingsHelper = new SystemSettingsHelper(context); mAppForegroundHelper = new SystemAppForegroundHelper(context); - mLocationUsageLogger = new LocationUsageLogger(); + mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context); + mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); + mLocationUsageLogger = new LocationUsageLogger(); + mLocationRequestStatistics = new LocationRequestStatistics(); } void onSystemReady() { mAppOpsHelper.onSystemReady(); + mLocationPermissionsHelper.onSystemReady(); mSettingsHelper.onSystemReady(); mAppForegroundHelper.onSystemReady(); + mLocationPowerSaveModeHelper.onSystemReady(); + mScreenInteractiveHelper.onSystemReady(); } @Override @@ -2957,6 +2981,11 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public LocationPermissionsHelper getLocationPermissionsHelper() { + return mLocationPermissionsHelper; + } + + @Override public SettingsHelper getSettingsHelper() { return mSettingsHelper; } @@ -2972,8 +3001,23 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override + public LocationPowerSaveModeHelper getLocationPowerSaveModeHelper() { + return mLocationPowerSaveModeHelper; + } + + @Override + public ScreenInteractiveHelper getScreenInteractiveHelper() { + return mScreenInteractiveHelper; + } + + @Override public LocationAttributionHelper getLocationAttributionHelper() { return mLocationAttributionHelper; } + + @Override + public LocationRequestStatistics getLocationRequestStatistics() { + return mLocationRequestStatistics; + } } } diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java index f37992a456ac..f6896b86f9b9 100644 --- a/services/core/java/com/android/server/location/PassiveProvider.java +++ b/services/core/java/com/android/server/location/PassiveProvider.java @@ -50,14 +50,10 @@ public class PassiveProvider extends AbstractLocationProvider { Criteria.POWER_LOW, Criteria.ACCURACY_COARSE); - private volatile boolean mReportLocation; - public PassiveProvider(Context context) { // using a direct executor is ok because this class has no locks that could deadlock super(DIRECT_EXECUTOR, CallerIdentity.fromContext(context)); - mReportLocation = false; - setProperties(PROPERTIES); setAllowed(true); } @@ -66,15 +62,11 @@ public class PassiveProvider extends AbstractLocationProvider { * Pass a location into the passive provider. */ public void updateLocation(Location location) { - if (mReportLocation) { - reportLocation(location); - } + reportLocation(location); } @Override - public void onSetRequest(ProviderRequest request) { - mReportLocation = request.reportLocation; - } + public void onSetRequest(ProviderRequest request) {} @Override protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {} diff --git a/services/core/java/com/android/server/location/geofence/GeofenceManager.java b/services/core/java/com/android/server/location/geofence/GeofenceManager.java index c855a12606b2..2d9734ef0553 100644 --- a/services/core/java/com/android/server/location/geofence/GeofenceManager.java +++ b/services/core/java/com/android/server/location/geofence/GeofenceManager.java @@ -16,7 +16,6 @@ package com.android.server.location.geofence; -import static android.Manifest.permission; import static android.location.LocationManager.KEY_PROXIMITY_ENTERING; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; @@ -44,8 +43,8 @@ import com.android.server.PendingIntentUtils; import com.android.server.location.LocationPermissions; import com.android.server.location.listeners.ListenerMultiplexer; import com.android.server.location.listeners.PendingIntentListenerRegistration; -import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; +import com.android.server.location.util.LocationPermissionsHelper; import com.android.server.location.util.LocationUsageLogger; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.UserInfoHelper; @@ -86,7 +85,7 @@ public class GeofenceManager extends // we store these values because we don't trust the listeners not to give us dupes, not to // spam us, and because checking the values may be more expensive - private boolean mAppOpsAllowed; + private boolean mPermitted; private @Nullable Location mCachedLocation; private float mCachedLocationDistanceM; @@ -101,7 +100,7 @@ public class GeofenceManager extends mWakeLock = Objects.requireNonNull(mContext.getSystemService(PowerManager.class)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - TAG + ":" + identity.getPackageName()); + TAG + ":" + identity.getPackageName()); mWakeLock.setReferenceCounted(true); mWakeLock.setWorkSource(identity.addToWorkSource(null)); } @@ -114,7 +113,8 @@ public class GeofenceManager extends @Override protected void onPendingIntentListenerRegister() { mGeofenceState = STATE_UNKNOWN; - mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE); + mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); } @Override @@ -127,18 +127,32 @@ public class GeofenceManager extends } } - boolean isAppOpsAllowed() { - return mAppOpsAllowed; + boolean isPermitted() { + return mPermitted; } - boolean onAppOpsChanged(String packageName) { + boolean onLocationPermissionsChanged(String packageName) { if (getIdentity().getPackageName().equals(packageName)) { - boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), - PERMISSION_FINE); - if (appOpsAllowed != mAppOpsAllowed) { - mAppOpsAllowed = appOpsAllowed; - return true; - } + return onLocationPermissionsChanged(); + } + + return false; + } + + boolean onLocationPermissionsChanged(int uid) { + if (getIdentity().getUid() == uid) { + return onLocationPermissionsChanged(); + } + + return false; + } + + private boolean onLocationPermissionsChanged() { + boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); + if (permitted != mPermitted) { + mPermitted = permitted; + return true; } return false; @@ -186,10 +200,10 @@ public class GeofenceManager extends mWakeLock.acquire(WAKELOCK_TIMEOUT_MS); try { - pendingIntent.send(mContext, 0, intent, - (pI, i, rC, rD, rE) -> mWakeLock.release(), - null, permission.ACCESS_FINE_LOCATION, - PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); + // send() only enforces permissions for broadcast intents, but since clients can + // select any kind of pending intent we do not rely on send() to enforce permissions + pendingIntent.send(mContext, 0, intent, (pI, i, rC, rD, rE) -> mWakeLock.release(), + null, null, PendingIntentUtils.createDontSendToRestrictedAppsBundle(null)); } catch (PendingIntent.CanceledException e) { mWakeLock.release(); removeRegistration(new GeofenceKey(pendingIntent, getRequest()), this); @@ -202,7 +216,7 @@ public class GeofenceManager extends builder.append(getIdentity()); ArraySet<String> flags = new ArraySet<>(1); - if (!mAppOpsAllowed) { + if (!mPermitted) { flags.add("na"); } if (!flags.isEmpty()) { @@ -224,10 +238,22 @@ public class GeofenceManager extends private final SettingsHelper.UserSettingChangedListener mLocationPackageBlacklistChangedListener = this::onLocationPackageBlacklistChanged; - private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged; + private final LocationPermissionsHelper.LocationPermissionsListener + mLocationPermissionsListener = + new LocationPermissionsHelper.LocationPermissionsListener() { + @Override + public void onLocationPermissionsChanged(String packageName) { + GeofenceManager.this.onLocationPermissionsChanged(packageName); + } + + @Override + public void onLocationPermissionsChanged(int uid) { + GeofenceManager.this.onLocationPermissionsChanged(uid); + } + }; protected final UserInfoHelper mUserInfoHelper; - protected final AppOpsHelper mAppOpsHelper; + protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final SettingsHelper mSettingsHelper; protected final LocationUsageLogger mLocationUsageLogger; @@ -241,7 +267,7 @@ public class GeofenceManager extends mContext = context.createAttributionContext(ATTRIBUTION_TAG); mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); - mAppOpsHelper = injector.getAppOpsHelper(); + mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mLocationUsageLogger = injector.getLocationUsageLogger(); } @@ -281,7 +307,7 @@ public class GeofenceManager extends @Override protected boolean isActive(GeofenceRegistration registration) { CallerIdentity identity = registration.getIdentity(); - return registration.isAppOpsAllowed() + return registration.isPermitted() && mUserInfoHelper.isCurrentUserId(identity.getUserId()) && mSettingsHelper.isLocationEnabled(identity.getUserId()) && !mSettingsHelper.isLocationPackageBlacklisted(identity.getUserId(), @@ -294,7 +320,7 @@ public class GeofenceManager extends mSettingsHelper.addOnLocationEnabledChangedListener(mLocationEnabledChangedListener); mSettingsHelper.addOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.addListener(mAppOpsChangedListener); + mLocationPermissionsHelper.addListener(mLocationPermissionsListener); } @Override @@ -303,7 +329,7 @@ public class GeofenceManager extends mSettingsHelper.removeOnLocationEnabledChangedListener(mLocationEnabledChangedListener); mSettingsHelper.removeOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.removeListener(mAppOpsChangedListener); + mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); } @Override @@ -434,7 +460,11 @@ public class GeofenceManager extends updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onAppOpsChanged(String packageName) { - updateRegistrations(registration -> registration.onAppOpsChanged(packageName)); + private void onLocationPermissionsChanged(String packageName) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); + } + + private void onLocationPermissionsChanged(int uid) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java index 53e660ad6475..0b7968be484b 100644 --- a/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/gnss/GnssListenerMultiplexer.java @@ -31,8 +31,8 @@ import com.android.server.LocalServices; import com.android.server.location.listeners.BinderListenerRegistration; import com.android.server.location.listeners.ListenerMultiplexer; import com.android.server.location.util.AppForegroundHelper; -import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; +import com.android.server.location.util.LocationPermissionsHelper; import com.android.server.location.util.SettingsHelper; import com.android.server.location.util.UserInfoHelper; import com.android.server.location.util.UserInfoHelper.UserListener; @@ -65,7 +65,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter // we store these values because we don't trust the listeners not to give us dupes, not to // spam us, and because checking the values may be more expensive private boolean mForeground; - private boolean mAppOpsAllowed; + private boolean mPermitted; protected GnssListenerRegistration(@Nullable TRequest request, CallerIdentity callerIdentity, TListener listener) { @@ -84,24 +84,39 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter return mForeground; } - boolean isAppOpsAllowed() { - return mAppOpsAllowed; + boolean isPermitted() { + return mPermitted; } @Override protected void onBinderListenerRegister() { - mAppOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), PERMISSION_FINE); + mPermitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); mForeground = mAppForegroundHelper.isAppForeground(getIdentity().getUid()); } - boolean onAppOpsChanged(String packageName) { + boolean onLocationPermissionsChanged(String packageName) { if (getIdentity().getPackageName().equals(packageName)) { - boolean appOpsAllowed = mAppOpsHelper.checkLocationAccess(getIdentity(), - PERMISSION_FINE); - if (appOpsAllowed != mAppOpsAllowed) { - mAppOpsAllowed = appOpsAllowed; - return true; - } + return onLocationPermissionsChanged(); + } + + return false; + } + + boolean onLocationPermissionsChanged(int uid) { + if (getIdentity().getUid() == uid) { + return onLocationPermissionsChanged(); + } + + return false; + } + + private boolean onLocationPermissionsChanged() { + boolean permitted = mLocationPermissionsHelper.hasLocationPermissions(PERMISSION_FINE, + getIdentity()); + if (permitted != mPermitted) { + mPermitted = permitted; + return true; } return false; @@ -125,7 +140,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter if (!mForeground) { flags.add("bg"); } - if (!mAppOpsAllowed) { + if (!mPermitted) { flags.add("na"); } if (!flags.isEmpty()) { @@ -141,7 +156,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter protected final UserInfoHelper mUserInfoHelper; protected final SettingsHelper mSettingsHelper; - protected final AppOpsHelper mAppOpsHelper; + protected final LocationPermissionsHelper mLocationPermissionsHelper; protected final AppForegroundHelper mAppForegroundHelper; protected final LocationManagerInternal mLocationManagerInternal; @@ -154,14 +169,26 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter private final SettingsHelper.UserSettingChangedListener mLocationPackageBlacklistChangedListener = this::onLocationPackageBlacklistChanged; - private final AppOpsHelper.LocationAppOpListener mAppOpsChangedListener = this::onAppOpsChanged; + private final LocationPermissionsHelper.LocationPermissionsListener + mLocationPermissionsListener = + new LocationPermissionsHelper.LocationPermissionsListener() { + @Override + public void onLocationPermissionsChanged(String packageName) { + GnssListenerMultiplexer.this.onLocationPermissionsChanged(packageName); + } + + @Override + public void onLocationPermissionsChanged(int uid) { + GnssListenerMultiplexer.this.onLocationPermissionsChanged(uid); + } + }; private final AppForegroundHelper.AppForegroundListener mAppForegroundChangedListener = this::onAppForegroundChanged; protected GnssListenerMultiplexer(Injector injector) { mUserInfoHelper = injector.getUserInfoHelper(); mSettingsHelper = injector.getSettingsHelper(); - mAppOpsHelper = injector.getAppOpsHelper(); + mLocationPermissionsHelper = injector.getLocationPermissionsHelper(); mAppForegroundHelper = injector.getAppForegroundHelper(); mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); @@ -208,7 +235,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter CallerIdentity identity = registration.getIdentity(); // TODO: this should be checking if the gps provider is enabled, not if location is enabled, // but this is the same for now. - return registration.isAppOpsAllowed() + return registration.isPermitted() && (registration.isForeground() || isBackgroundRestrictionExempt(identity)) && mUserInfoHelper.isCurrentUserId(identity.getUserId()) && mSettingsHelper.isLocationEnabled(identity.getUserId()) @@ -241,7 +268,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mBackgroundThrottlePackageWhitelistChangedListener); mSettingsHelper.addOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.addListener(mAppOpsChangedListener); + mLocationPermissionsHelper.addListener(mLocationPermissionsListener); mAppForegroundHelper.addListener(mAppForegroundChangedListener); } @@ -257,7 +284,7 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter mBackgroundThrottlePackageWhitelistChangedListener); mSettingsHelper.removeOnLocationPackageBlacklistChangedListener( mLocationPackageBlacklistChangedListener); - mAppOpsHelper.removeListener(mAppOpsChangedListener); + mLocationPermissionsHelper.removeListener(mLocationPermissionsListener); mAppForegroundHelper.removeListener(mAppForegroundChangedListener); } @@ -279,8 +306,12 @@ public abstract class GnssListenerMultiplexer<TRequest, TListener extends IInter updateRegistrations(registration -> registration.getIdentity().getUserId() == userId); } - private void onAppOpsChanged(String packageName) { - updateRegistrations(registration -> registration.onAppOpsChanged(packageName)); + private void onLocationPermissionsChanged(String packageName) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(packageName)); + } + + private void onLocationPermissionsChanged(int uid) { + updateRegistrations(registration -> registration.onLocationPermissionsChanged(uid)); } private void onAppForegroundChanged(int uid, boolean foreground) { 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 8aaf4bf6d8b0..8004ec70aaf3 100644 --- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java @@ -2008,9 +2008,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final class FusedLocationListener extends LocationChangeListener { @Override public void onLocationChanged(Location location) { - if (LocationManager.FUSED_PROVIDER.equals(location.getProvider())) { - injectBestLocation(location); - } + injectBestLocation(location); } } diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java index 58e725ca152d..8e81f29550d6 100644 --- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java +++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java @@ -18,10 +18,9 @@ package com.android.server.location.gnss; import static android.location.LocationManager.GPS_PROVIDER; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; - import android.Manifest; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.content.Context; import android.location.GnssAntennaInfo; import android.location.GnssMeasurementCorrections; @@ -47,10 +46,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; -import com.android.server.location.util.AppForegroundHelper; import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; -import com.android.server.location.util.SettingsHelper; import java.io.FileDescriptor; import java.util.List; @@ -68,9 +65,7 @@ public class GnssManagerService implements GnssNative.Callbacks { } private final Context mContext; - private final SettingsHelper mSettingsHelper; private final AppOpsHelper mAppOpsHelper; - private final AppForegroundHelper mAppForegroundHelper; private final LocationManagerInternal mLocationManagerInternal; private final GnssLocationProvider mGnssLocationProvider; @@ -109,9 +104,7 @@ public class GnssManagerService implements GnssNative.Callbacks { GnssNative.initialize(); mContext = context.createAttributionContext(ATTRIBUTION_ID); - mSettingsHelper = injector.getSettingsHelper(); mAppOpsHelper = injector.getAppOpsHelper(); - mAppForegroundHelper = injector.getAppForegroundHelper(); mLocationManagerInternal = LocalServices.getService(LocationManagerInternal.class); if (gnssLocationProvider == null) { @@ -192,9 +185,10 @@ public class GnssManagerService implements GnssNative.Callbacks { public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName, String attributionTag) { mContext.enforceCallingOrSelfPermission(Manifest.permission.LOCATION_HARDWARE, null); + mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION, null); CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag); - if (!mAppOpsHelper.checkLocationAccess(identity, PERMISSION_FINE)) { + if (!mAppOpsHelper.checkOpNoThrow(AppOpsManager.OP_FINE_LOCATION, identity)) { return false; } diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 9227a177f861..0815d46a735d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssMeasurementsEvent; import android.location.GnssRequest; import android.location.IGnssMeasurementsListener; @@ -30,6 +30,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationUsageLogger; import com.android.server.location.util.SettingsHelper; @@ -47,6 +48,7 @@ public class GnssMeasurementsProvider extends GnssListenerMultiplexer<GnssRequest, IGnssMeasurementsListener, Boolean> implements SettingsHelper.GlobalSettingChangedListener { + private final AppOpsHelper mAppOpsHelper; private final LocationUsageLogger mLogger; private final GnssMeasurementProviderNative mNative; @@ -57,6 +59,7 @@ public class GnssMeasurementsProvider extends @VisibleForTesting public GnssMeasurementsProvider(Injector injector, GnssMeasurementProviderNative aNative) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mLogger = injector.getLocationUsageLogger(); mNative = aNative; } @@ -163,7 +166,8 @@ public class GnssMeasurementsProvider extends */ public void onMeasurementsAvailable(GnssMeasurementsEvent event) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onGnssMeasurementsReceived(event); } else { return null; diff --git a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java index a07fbe41c975..7dcffc664f52 100644 --- a/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssNavigationMessageProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssNavigationMessage; import android.location.IGnssNavigationMessageListener; import android.location.util.identity.CallerIdentity; @@ -27,6 +27,7 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; /** @@ -39,6 +40,7 @@ import com.android.server.location.util.Injector; public class GnssNavigationMessageProvider extends GnssListenerMultiplexer<Void, IGnssNavigationMessageListener, Void> { + private final AppOpsHelper mAppOpsHelper; private final GnssNavigationMessageProviderNative mNative; public GnssNavigationMessageProvider(Injector injector) { @@ -49,6 +51,7 @@ public class GnssNavigationMessageProvider extends public GnssNavigationMessageProvider(Injector injector, GnssNavigationMessageProviderNative aNative) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mNative = aNative; } @@ -90,7 +93,8 @@ public class GnssNavigationMessageProvider extends */ public void onNavigationMessageAvailable(GnssNavigationMessage event) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onGnssNavigationMessageReceived(event); } else { return null; diff --git a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java index d33b05866877..19f79273c992 100644 --- a/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssStatusProvider.java @@ -16,10 +16,10 @@ package com.android.server.location.gnss; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.app.AppOpsManager; import android.location.GnssStatus; import android.location.IGnssStatusListener; import android.location.util.identity.CallerIdentity; @@ -27,6 +27,7 @@ import android.os.IBinder; import android.stats.location.LocationStatsEnums; import android.util.Log; +import com.android.server.location.util.AppOpsHelper; import com.android.server.location.util.Injector; import com.android.server.location.util.LocationUsageLogger; @@ -35,10 +36,12 @@ import com.android.server.location.util.LocationUsageLogger; */ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatusListener, Void> { + private final AppOpsHelper mAppOpsHelper; private final LocationUsageLogger mLogger; public GnssStatusProvider(Injector injector) { super(injector); + mAppOpsHelper = injector.getAppOpsHelper(); mLogger = injector.getLocationUsageLogger(); } @@ -113,7 +116,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu */ public void onSvStatusChanged(GnssStatus gnssStatus) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onSvStatusChanged(gnssStatus); } else { return null; @@ -126,7 +130,8 @@ public class GnssStatusProvider extends GnssListenerMultiplexer<Void, IGnssStatu */ public void onNmeaReceived(long timestamp, String nmea) { deliverToListeners(registration -> { - if (mAppOpsHelper.noteLocationAccess(registration.getIdentity(), PERMISSION_FINE)) { + if (mAppOpsHelper.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, + registration.getIdentity())) { return listener -> listener.onNmeaReceived(timestamp, nmea); } else { return null; diff --git a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java index f5889ceeed6a..528cf8acd5b3 100644 --- a/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java +++ b/services/core/java/com/android/server/location/listeners/ListenerMultiplexer.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.os.Binder; import android.os.Build; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.Pair; @@ -40,8 +41,8 @@ import java.util.function.Predicate; * A base class to multiplex client listener registrations within system server. Registrations are * divided into two categories, active registrations and inactive registrations, as defined by * {@link #isActive(ListenerRegistration)}. If a registration's active state changes, - * {@link #updateRegistrations(Predicate)} or {@link #updateRegistration(Object, Predicate)} must be - * invoked and return true for any registration whose active state may have changed. + * {@link #updateRegistrations(Predicate)} must be invoked and return true for any registration + * whose active state may have changed. * * Callbacks invoked for various changes will always be ordered according to this lifecycle list: * @@ -217,7 +218,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, mRegistrations.put(key, registration); } - if (wasEmpty) { onRegister(); } @@ -268,7 +268,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TKey key = mRegistrations.keyAt(i); if (predicate.test(key)) { removeRegistration(key, mRegistrations.valueAt(i)); @@ -287,7 +288,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, * completely at some later time. */ protected final void removeRegistration(@NonNull Object key, - @NonNull ListenerRegistration registration) { + @NonNull ListenerRegistration<?, ?> registration) { synchronized (mRegistrations) { int index = mRegistrations.indexOfKey(key); if (index < 0) { @@ -353,7 +354,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, } ArrayList<TRegistration> actives = new ArrayList<>(mRegistrations.size()); - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { actives.add(registration); @@ -395,7 +397,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, protected final void updateService(Predicate<TRegistration> predicate) { synchronized (mRegistrations) { boolean updateService = false; - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (predicate.test(registration) && registration.isActive()) { updateService = true; @@ -434,7 +437,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (predicate.test(registration)) { onRegistrationActiveChanged(registration); @@ -446,33 +450,6 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, } } - /** - * Evaluates the predicate on the registration with the given key. The predicate should return - * true if the active state of the registration may have changed as a result. Any - * {@link #updateService()} invocations made while this method is executing will be deferred - * until after the method is complete so as to avoid redundant work. - */ - protected final void updateRegistration(TKey key, @NonNull Predicate<TRegistration> predicate) { - synchronized (mRegistrations) { - // since updating a registration can invoke a variety of callbacks, we need to ensure - // those callbacks themselves do not re-enter, as this could lead to out-of-order - // callbacks. note that try-with-resources ordering is meaningful here as well. we want - // to close the reentrancy guard first, as this may generate additional service updates, - // then close the update service buffer. - long identity = Binder.clearCallingIdentity(); - try (UpdateServiceBuffer ignored1 = mUpdateServiceBuffer.acquire(); - ReentrancyGuard ignored2 = mReentrancyGuard.acquire()) { - - TRegistration registration = mRegistrations.get(key); - if (registration != null && predicate.test(registration)) { - onRegistrationActiveChanged(registration); - } - } finally { - Binder.restoreCallingIdentity(identity); - } - } - } - @GuardedBy("mRegistrations") private void onRegistrationActiveChanged(TRegistration registration) { if (Build.IS_DEBUGGABLE) { @@ -511,7 +488,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, synchronized (mRegistrations) { long identity = Binder.clearCallingIdentity(); try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { ListenerOperation<TListener> operation = function.apply(registration); @@ -537,7 +515,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, synchronized (mRegistrations) { long identity = Binder.clearCallingIdentity(); try (ReentrancyGuard ignored = mReentrancyGuard.acquire()) { - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); if (registration.isActive()) { execute(registration, operation); @@ -571,7 +550,8 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, ipw.println("listeners:"); ipw.increaseIndent(); - for (int i = 0; i < mRegistrations.size(); i++) { + final int size = mRegistrations.size(); + for (int i = 0; i < size; i++) { TRegistration registration = mRegistrations.valueAt(i); ipw.print(registration); if (!registration.isActive()) { @@ -612,23 +592,33 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, */ private final class ReentrancyGuard implements AutoCloseable { + @GuardedBy("mRegistrations") private int mGuardCount; - private @Nullable ArrayList<Pair<Object, ListenerRegistration>> mScheduledRemovals; + @GuardedBy("mRegistrations") + private @Nullable ArraySet<Pair<Object, ListenerRegistration<?, ?>>> mScheduledRemovals; ReentrancyGuard() { mGuardCount = 0; mScheduledRemovals = null; } + @GuardedBy("mRegistrations") boolean isReentrant() { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mRegistrations)); + } return mGuardCount != 0; } - void markForRemoval(Object key, ListenerRegistration registration) { + @GuardedBy("mRegistrations") + void markForRemoval(Object key, ListenerRegistration<?, ?> registration) { + if (Build.IS_DEBUGGABLE) { + Preconditions.checkState(Thread.holdsLock(mRegistrations)); + } Preconditions.checkState(isReentrant()); if (mScheduledRemovals == null) { - mScheduledRemovals = new ArrayList<>(mRegistrations.size()); + mScheduledRemovals = new ArraySet<>(mRegistrations.size()); } mScheduledRemovals.add(new Pair<>(key, registration)); } @@ -640,7 +630,7 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, @Override public void close() { - ArrayList<Pair<Object, ListenerRegistration>> scheduledRemovals = null; + ArraySet<Pair<Object, ListenerRegistration<?, ?>>> scheduledRemovals = null; Preconditions.checkState(mGuardCount > 0); if (--mGuardCount == 0) { @@ -650,8 +640,10 @@ public abstract class ListenerMultiplexer<TKey, TRequest, TListener, if (scheduledRemovals != null) { try (UpdateServiceBuffer ignored = mUpdateServiceBuffer.acquire()) { - for (int i = 0; i < scheduledRemovals.size(); i++) { - Pair<Object, ListenerRegistration> pair = scheduledRemovals.get(i); + final int size = scheduledRemovals.size(); + for (int i = 0; i < size; i++) { + Pair<Object, ListenerRegistration<?, ?>> pair = scheduledRemovals.valueAt( + i); removeRegistration(pair.first, pair.second); } } diff --git a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java index e529a7d81b70..6a815ead9f9f 100644 --- a/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java +++ b/services/core/java/com/android/server/location/listeners/RemovableListenerRegistration.java @@ -61,7 +61,7 @@ public abstract class RemovableListenerRegistration<TRequest, TListener> extends * Removes this registration. Does nothing if invoked before {@link #onRegister(Object)} or * after {@link #onUnregister()}. It is safe to invoke this from within either function. */ - public void remove() { + public final void remove() { Object key = mKey; if (key != null) { getOwner().removeRegistration(key, this); diff --git a/services/core/java/com/android/server/location/util/AppOpsHelper.java b/services/core/java/com/android/server/location/util/AppOpsHelper.java index 3e42f27da78c..1578289d53b4 100644 --- a/services/core/java/com/android/server/location/util/AppOpsHelper.java +++ b/services/core/java/com/android/server/location/util/AppOpsHelper.java @@ -16,15 +16,8 @@ package com.android.server.location.util; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_LOCATION; - -import android.app.AppOpsManager; import android.location.util.identity.CallerIdentity; -import com.android.server.location.LocationPermissions; -import com.android.server.location.LocationPermissions.PermissionLevel; - import java.util.concurrent.CopyOnWriteArrayList; /** @@ -70,84 +63,27 @@ public abstract class AppOpsHelper { } /** - * Checks if the given identity may have locations delivered without noting that a location is - * being delivered. This is a looser guarantee than - * {@link #noteLocationAccess(CallerIdentity, int)}, and this function does not validate package - * arguments and so should not be used with unvalidated arguments or before actually delivering - * locations. - * - * @see AppOpsManager#checkOpNoThrow(int, int, String) - */ - public final boolean checkLocationAccess(CallerIdentity callerIdentity, - @PermissionLevel int permissionLevel) { - if (permissionLevel == LocationPermissions.PERMISSION_NONE) { - return false; - } - - return checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), callerIdentity); - } - - /** - * Notes location access to the given identity, ie, location delivery. This method should be - * called right before a location is delivered, and if it returns false, the location should not - * be delivered. - */ - public final boolean noteLocationAccess(CallerIdentity identity, - @PermissionLevel int permissionLevel) { - if (permissionLevel == LocationPermissions.PERMISSION_NONE) { - return false; - } - - return noteOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity); - } - - /** - * Notifies app ops that the given identity is using location at normal/low power levels. If - * this function returns false, do not later call - * {@link #stopLocationMonitoring(CallerIdentity)}. + * Starts the given appop. */ - public final boolean startLocationMonitoring(CallerIdentity identity) { - return startOpNoThrow(OP_MONITOR_LOCATION, identity); - } + public abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is no longer using location at normal/low power - * levels. + * Finishes the given appop. */ - public final void stopLocationMonitoring(CallerIdentity identity) { - finishOp(OP_MONITOR_LOCATION, identity); - } + public abstract void finishOp(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is using location at high levels. If this function - * returns false, do not later call {@link #stopLocationMonitoring(CallerIdentity)}. + * Checks the given appop. */ - public final boolean startHighPowerLocationMonitoring(CallerIdentity identity) { - return startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity); - } + public abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity); /** - * Notifies app ops that the given identity is no longer using location at high power levels. + * Notes the given appop (and may throw a security exception). */ - public final void stopHighPowerLocationMonitoring(CallerIdentity identity) { - finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); - } + public abstract boolean noteOp(int appOp, CallerIdentity callerIdentity); /** - * Notes access to any mock location APIs. If this call returns false, access to the APIs should - * silently fail. + * Notes the given appop. */ - public final boolean noteMockLocationAccess(CallerIdentity callerIdentity) { - return noteOp(AppOpsManager.OP_MOCK_LOCATION, callerIdentity); - } - - protected abstract boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity); - - protected abstract void finishOp(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean noteOp(int appOp, CallerIdentity callerIdentity); - - protected abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity); + public abstract boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity); } diff --git a/services/core/java/com/android/server/location/util/Injector.java b/services/core/java/com/android/server/location/util/Injector.java index e16df5dc26cd..379b303bbfc3 100644 --- a/services/core/java/com/android/server/location/util/Injector.java +++ b/services/core/java/com/android/server/location/util/Injector.java @@ -17,6 +17,7 @@ package com.android.server.location.util; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.location.LocationRequestStatistics; /** * Injects various location dependencies so that they may be controlled by tests. @@ -30,15 +31,27 @@ public interface Injector { /** Returns an AppOpsHelper. */ AppOpsHelper getAppOpsHelper(); + /** Returns a LocationPermissionsHelper. */ + LocationPermissionsHelper getLocationPermissionsHelper(); + /** Returns a SettingsHelper. */ SettingsHelper getSettingsHelper(); /** Returns an AppForegroundHelper. */ AppForegroundHelper getAppForegroundHelper(); - /** Returns a LocationUsageLogger. */ - LocationUsageLogger getLocationUsageLogger(); + /** Returns a LocationPowerSaveModeHelper. */ + LocationPowerSaveModeHelper getLocationPowerSaveModeHelper(); + + /** Returns a ScreenInteractiveHelper. */ + ScreenInteractiveHelper getScreenInteractiveHelper(); /** Returns a LocationAttributionHelper. */ LocationAttributionHelper getLocationAttributionHelper(); + + /** Returns a LocationUsageLogger. */ + LocationUsageLogger getLocationUsageLogger(); + + /** Returns a LocationRequestStatistics. */ + LocationRequestStatistics getLocationRequestStatistics(); } diff --git a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java index 8fe09412c166..bc3ac0ff2e48 100644 --- a/services/core/java/com/android/server/location/util/LocationAttributionHelper.java +++ b/services/core/java/com/android/server/location/util/LocationAttributionHelper.java @@ -16,9 +16,16 @@ package com.android.server.location.util; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; + +import static com.android.server.location.LocationManagerService.D; +import static com.android.server.location.LocationManagerService.TAG; + import android.location.util.identity.CallerIdentity; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -83,7 +90,7 @@ public class LocationAttributionHelper { i -> new ArraySet<>()); boolean empty = keySet.isEmpty(); if (keySet.add(new ProviderListener(provider, key)) && empty) { - if (!mAppOpsHelper.startLocationMonitoring(identity)) { + if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { mAttributions.remove(identity); } } @@ -99,7 +106,7 @@ public class LocationAttributionHelper { if (keySet != null && keySet.remove(new ProviderListener(provider, key)) && keySet.isEmpty()) { mAttributions.remove(identity); - mAppOpsHelper.stopLocationMonitoring(identity); + mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); } } @@ -113,14 +120,18 @@ public class LocationAttributionHelper { i -> new ArraySet<>()); boolean empty = keySet.isEmpty(); if (keySet.add(new ProviderListener(provider, key)) && empty) { - if (!mAppOpsHelper.startHighPowerLocationMonitoring(identity)) { + if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { + if (D) { + Log.v(TAG, "starting high power location attribution for " + identity); + } + } else { mHighPowerAttributions.remove(identity); } } } /** - * Report high power location usage has stopped for the given caller on the given provider, + * Report high power location usage has stopped for the given caller on the given provider, * with a unique key. */ public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String provider, @@ -128,8 +139,11 @@ public class LocationAttributionHelper { Set<ProviderListener> keySet = mHighPowerAttributions.get(identity); if (keySet != null && keySet.remove(new ProviderListener(provider, key)) && keySet.isEmpty()) { + if (D) { + Log.v(TAG, "stopping high power location attribution for " + identity); + } mHighPowerAttributions.remove(identity); - mAppOpsHelper.stopHighPowerLocationMonitoring(identity); + mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); } } } diff --git a/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java new file mode 100644 index 000000000000..daf56797c0c9 --- /dev/null +++ b/services/core/java/com/android/server/location/util/LocationPermissionsHelper.java @@ -0,0 +1,107 @@ +/* + * 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.location.util; + +import static com.android.server.location.LocationPermissions.PERMISSION_NONE; + +import android.location.util.identity.CallerIdentity; + +import com.android.server.location.LocationPermissions; +import com.android.server.location.LocationPermissions.PermissionLevel; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides helpers and listeners for appops. + */ +public abstract class LocationPermissionsHelper { + + /** + * Listener for current user changes. + */ + public interface LocationPermissionsListener { + + /** + * Called when something has changed about location permissions for the given package. + */ + void onLocationPermissionsChanged(String packageName); + + /** + * Called when something has changed about location permissions for the given uid. + */ + void onLocationPermissionsChanged(int uid); + } + + private final CopyOnWriteArrayList<LocationPermissionsListener> mListeners; + private final AppOpsHelper mAppOps; + + public LocationPermissionsHelper(AppOpsHelper appOps) { + mListeners = new CopyOnWriteArrayList<>(); + mAppOps = appOps; + + mAppOps.addListener(this::onAppOpsChanged); + } + + protected final void notifyLocationPermissionsChanged(String packageName) { + for (LocationPermissionsListener listener : mListeners) { + listener.onLocationPermissionsChanged(packageName); + } + } + + protected final void notifyLocationPermissionsChanged(int uid) { + for (LocationPermissionsListener listener : mListeners) { + listener.onLocationPermissionsChanged(uid); + } + } + + private void onAppOpsChanged(String packageName) { + notifyLocationPermissionsChanged(packageName); + } + + /** + * Adds a listener for location permissions events. Callbacks occur on an unspecified thread. + */ + public final void addListener(LocationPermissionsListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for location permissions events. + */ + public final void removeListener(LocationPermissionsListener listener) { + mListeners.remove(listener); + } + + /** + * Returns true if the given identity may access location at the given permissions level, taking + * into account both permissions and appops. + */ + public final boolean hasLocationPermissions(@PermissionLevel int permissionLevel, + CallerIdentity identity) { + if (permissionLevel == PERMISSION_NONE) { + return false; + } + + if (!hasPermission(LocationPermissions.asPermission(permissionLevel), identity)) { + return false; + } + + return mAppOps.checkOpNoThrow(permissionLevel, identity); + } + + protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity); +} diff --git a/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..a9a8c50f11dc --- /dev/null +++ b/services/core/java/com/android/server/location/util/LocationPowerSaveModeHelper.java @@ -0,0 +1,71 @@ +/* + * 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.location.util; + +import android.os.PowerManager.LocationPowerSaveMode; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides accessors and listeners for location power save mode. + */ +public abstract class LocationPowerSaveModeHelper { + + /** + * Listener for location power save mode changes. + */ + public interface LocationPowerSaveModeChangedListener { + /** + * Called when the location power save mode changes. + */ + void onLocationPowerSaveModeChanged(@LocationPowerSaveMode int locationPowerSaveMode); + } + + private final CopyOnWriteArrayList<LocationPowerSaveModeChangedListener> mListeners; + + public LocationPowerSaveModeHelper() { + mListeners = new CopyOnWriteArrayList<>(); + } + + /** + * Add a listener for changes to location power save mode. Callbacks occur on an unspecified + * thread. + */ + public final void addListener(LocationPowerSaveModeChangedListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for changes to location power save mode. + */ + public final void removeListener(LocationPowerSaveModeChangedListener listener) { + mListeners.remove(listener); + } + + protected final void notifyLocationPowerSaveModeChanged( + @LocationPowerSaveMode int locationPowerSaveMode) { + for (LocationPowerSaveModeChangedListener listener : mListeners) { + listener.onLocationPowerSaveModeChanged(locationPowerSaveMode); + } + } + + /** + * Returns the current location power save mode. + */ + @LocationPowerSaveMode + public abstract int getLocationPowerSaveMode(); +} diff --git a/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java new file mode 100644 index 000000000000..d47bce31ed23 --- /dev/null +++ b/services/core/java/com/android/server/location/util/ScreenInteractiveHelper.java @@ -0,0 +1,67 @@ +/* + * 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.location.util; + +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Provides accessors and listeners for screen interactive state (screen on/off). + */ +public abstract class ScreenInteractiveHelper { + + /** + * Listener for screen interactive changes. + */ + public interface ScreenInteractiveChangedListener { + /** + * Called when the screen interative state changes. + */ + void onScreenInteractiveChanged(boolean isInteractive); + } + + private final CopyOnWriteArrayList<ScreenInteractiveChangedListener> mListeners; + + public ScreenInteractiveHelper() { + mListeners = new CopyOnWriteArrayList<>(); + } + + /** + * Add a listener for changes to screen interactive state. Callbacks occur on an unspecified + * thread. + */ + public final void addListener(ScreenInteractiveChangedListener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener for changes to screen interactive state. + */ + public final void removeListener(ScreenInteractiveChangedListener listener) { + mListeners.remove(listener); + } + + protected final void notifyScreenInteractiveChanged(boolean interactive) { + for (ScreenInteractiveChangedListener listener : mListeners) { + listener.onScreenInteractiveChanged(interactive); + } + } + + /** + * Returns true if the screen is currently interactive, and false otherwise. + */ + public abstract boolean isInteractive(); +} diff --git a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java index a95383695ae8..cfb7697a8dfc 100644 --- a/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java +++ b/services/core/java/com/android/server/location/util/SystemAppOpsHelper.java @@ -57,7 +57,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -75,7 +75,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected void finishOp(int appOp, CallerIdentity callerIdentity) { + public void finishOp(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -91,7 +91,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -106,7 +106,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOp(int appOp, CallerIdentity callerIdentity) { + public boolean noteOp(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); @@ -123,7 +123,7 @@ public class SystemAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { Preconditions.checkState(mAppOps != null); long identity = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.java new file mode 100644 index 000000000000..b9c0ddef04ab --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemLocationPermissionsHelper.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.server.location.util; + +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import android.content.Context; +import android.location.util.identity.CallerIdentity; +import android.os.Binder; + +import com.android.server.FgThread; + +/** + * Provides accessors and listeners for location permissions, including appops. + */ +public class SystemLocationPermissionsHelper extends LocationPermissionsHelper { + + private final Context mContext; + + private boolean mInited; + + public SystemLocationPermissionsHelper(Context context, AppOpsHelper appOps) { + super(appOps); + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mInited) { + return; + } + + mContext.getPackageManager().addOnPermissionsChangeListener( + uid -> { + // invoked on ui thread, move to fg thread so ui thread isn't blocked + FgThread.getHandler().post(() -> notifyLocationPermissionsChanged(uid)); + }); + mInited = true; + } + + @Override + protected boolean hasPermission(String permission, CallerIdentity callerIdentity) { + long identity = Binder.clearCallingIdentity(); + try { + return mContext.checkPermission(permission, callerIdentity.getPid(), + callerIdentity.getUid()) == PERMISSION_GRANTED; + } finally { + Binder.restoreCallingIdentity(identity); + } + } +} diff --git a/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..c8d8202157f0 --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemLocationPowerSaveModeHelper.java @@ -0,0 +1,87 @@ +/* + * 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.location.util; + +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManager.LocationPowerSaveMode; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; + +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; +import com.android.server.LocalServices; + +import java.util.Objects; +import java.util.function.Consumer; + +/** + * Provides accessors and listeners for location power save mode. + */ +public class SystemLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper implements + Consumer<PowerSaveState> { + + private final Context mContext; + private boolean mReady; + + @LocationPowerSaveMode + private volatile int mLocationPowerSaveMode; + + public SystemLocationPowerSaveModeHelper(Context context) { + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mReady) { + return; + } + + LocalServices.getService(PowerManagerInternal.class).registerLowPowerModeObserver( + PowerManager.ServiceType.LOCATION, this); + mLocationPowerSaveMode = Objects.requireNonNull( + mContext.getSystemService(PowerManager.class)).getLocationPowerSaveMode(); + + mReady = true; + } + + @Override + public void accept(PowerSaveState powerSaveState) { + int locationPowerSaveMode; + if (!powerSaveState.batterySaverEnabled) { + locationPowerSaveMode = PowerManager.LOCATION_MODE_NO_CHANGE; + } else { + locationPowerSaveMode = powerSaveState.locationMode; + } + + if (locationPowerSaveMode == mLocationPowerSaveMode) { + return; + } + + mLocationPowerSaveMode = locationPowerSaveMode; + + // invoked on ui thread, move to fg thread so we don't block the ui thread + FgThread.getHandler().post(() -> notifyLocationPowerSaveModeChanged(locationPowerSaveMode)); + } + + @LocationPowerSaveMode + @Override + public int getLocationPowerSaveMode() { + Preconditions.checkState(mReady); + return mLocationPowerSaveMode; + } +} diff --git a/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java new file mode 100644 index 000000000000..70cedac20868 --- /dev/null +++ b/services/core/java/com/android/server/location/util/SystemScreenInteractiveHelper.java @@ -0,0 +1,85 @@ +/* + * 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.location.util; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.UserHandle; + +import com.android.internal.util.Preconditions; +import com.android.server.FgThread; + +/** + * Provides accessors and listeners for screen interactive state (screen on/off). + */ +public class SystemScreenInteractiveHelper extends ScreenInteractiveHelper { + + private final Context mContext; + + private boolean mReady; + + private volatile boolean mIsInteractive; + + public SystemScreenInteractiveHelper(Context context) { + mContext = context; + } + + /** Called when system is ready. */ + public void onSystemReady() { + if (mReady) { + return; + } + + IntentFilter screenIntentFilter = new IntentFilter(); + screenIntentFilter.addAction(Intent.ACTION_SCREEN_OFF); + screenIntentFilter.addAction(Intent.ACTION_SCREEN_ON); + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + boolean interactive; + if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { + interactive = true; + } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + interactive = false; + } else { + return; + } + + onScreenInteractiveChanged(interactive); + } + }, UserHandle.ALL, screenIntentFilter, null, FgThread.getHandler()); + + mReady = true; + } + + private void onScreenInteractiveChanged(boolean interactive) { + if (interactive == mIsInteractive) { + return; + } + + mIsInteractive = interactive; + notifyScreenInteractiveChanged(interactive); + } + + @Override + public boolean isInteractive() { + Preconditions.checkState(mReady); + return mIsInteractive; + } +} diff --git a/services/core/java/com/android/server/media/OWNERS b/services/core/java/com/android/server/media/OWNERS index b460cb5b23ea..2e2d812c058e 100644 --- a/services/core/java/com/android/server/media/OWNERS +++ b/services/core/java/com/android/server/media/OWNERS @@ -2,6 +2,7 @@ elaurent@google.com hdmoon@google.com insun@google.com jaewan@google.com +jinpark@google.com klhyun@google.com lajos@google.com sungsoo@google.com diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00cb22e9d4b5..391a08db6716 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -459,12 +459,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final int returnCode = args.argi1; args.recycle(); - final boolean showNotification; - synchronized (mLock) { - showNotification = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(); - } sendOnPackageInstalled(mContext, statusReceiver, sessionId, - showNotification, userId, + isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, packageName, returnCode, message, extras); break; @@ -494,8 +490,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { /** * @return {@code true} iff the installing is app an device owner or affiliated profile owner. */ - @GuardedBy("mLock") - private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked() { + private boolean isInstallerDeviceOwnerOrAffiliatedProfileOwner() { + assertNotLocked("isInstallerDeviceOwnerOrAffiliatedProfileOwner"); + // It is safe to access mInstallerUid and mInstallSource without lock + // because they are immutable after sealing. + assertSealed("isInstallerDeviceOwnerOrAffiliatedProfileOwner"); if (userId != UserHandle.getUserId(mInstallerUid)) { return false; } @@ -513,12 +512,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * * @return {@code true} iff we need to ask to confirm the permissions? */ - @GuardedBy("mLock") - private boolean needToAskForPermissionsLocked() { - if (mPermissionsManuallyAccepted) { - return false; + private boolean needToAskForPermissions() { + final String packageName; + synchronized (mLock) { + if (mPermissionsManuallyAccepted) { + return false; + } + packageName = mPackageName; } + // It is safe to access mInstallerUid and mInstallSource without lock + // because they are immutable after sealing. final boolean isInstallPermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); @@ -528,7 +532,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isUpdatePermissionGranted = (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGE_UPDATES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final int targetPackageUid = mPm.getPackageUid(mPackageName, 0, userId); + final int targetPackageUid = mPm.getPackageUid(packageName, 0, userId); final boolean isPermissionGranted = isInstallPermissionGranted || (isUpdatePermissionGranted && targetPackageUid != -1) || (isSelfUpdatePermissionGranted && targetPackageUid == mInstallerUid); @@ -540,7 +544,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // Device owners and affiliated profile owners are allowed to silently install packages, so // the permission check is waived if the installer is the device owner. return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot - || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked()); + || isInstallerSystem || isInstallerDeviceOwnerOrAffiliatedProfileOwner()); } public PackageInstallerSession(PackageInstallerService.InternalCallback callback, @@ -740,6 +744,18 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + private void assertNotLocked(String cookie) { + if (Thread.holdsLock(mLock)) { + throw new IllegalStateException(cookie + " is holding mLock"); + } + } + + private void assertSealed(String cookie) { + if (!isSealed()) { + throw new IllegalStateException(cookie + " before sealing"); + } + } + @GuardedBy("mLock") private void assertPreparedAndNotSealedLocked(String cookie) { assertPreparedAndNotCommittedOrDestroyedLocked(cookie); @@ -1693,11 +1709,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } private void handleInstall() { - final boolean needsLogging; - synchronized (mLock) { - needsLogging = isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(); - } - if (needsLogging) { + if (isInstallerDeviceOwnerOrAffiliatedProfileOwner()) { DevicePolicyEventLogger .createEvent(DevicePolicyEnums.INSTALL_PACKAGE) .setAdmin(mInstallSource.installerPackageName) @@ -1724,9 +1736,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { List<PackageInstallerSession> childSessions = getChildSessionsNotLocked(); try { - synchronized (mLock) { - installNonStagedLocked(childSessions); - } + installNonStaged(childSessions); } catch (PackageManagerException e) { final String completeMsg = ExceptionUtils.getCompleteMessage(e); Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg); @@ -1735,11 +1745,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - @GuardedBy("mLock") - private void installNonStagedLocked(List<PackageInstallerSession> childSessions) + private void installNonStaged(List<PackageInstallerSession> childSessions) throws PackageManagerException { final PackageManagerService.ActiveInstallSession installingSession = - makeSessionActiveLocked(); + makeSessionActive(); if (installingSession == null) { return; } @@ -1752,7 +1761,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final PackageInstallerSession session = childSessions.get(i); try { final PackageManagerService.ActiveInstallSession installingChildSession = - session.makeSessionActiveLocked(); + session.makeSessionActive(); if (installingChildSession != null) { installingChildSessions.add(installingChildSession); } @@ -1762,8 +1771,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } if (!success) { - sendOnPackageInstalled(mContext, mRemoteStatusReceiver, sessionId, - isInstallerDeviceOwnerOrAffiliatedProfileOwnerLocked(), userId, null, + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendOnPackageInstalled(mContext, statusReceiver, sessionId, + isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId, null, failure.error, failure.getLocalizedMessage(), null); return; } @@ -1778,41 +1791,58 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * {@link PackageManagerService.ActiveInstallSession} representing this new staged state or null * in case permissions need to be requested before install can proceed. */ - @GuardedBy("mLock") - private PackageManagerService.ActiveInstallSession makeSessionActiveLocked() + private PackageManagerService.ActiveInstallSession makeSessionActive() throws PackageManagerException { - if (mRelinquished) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, - "Session relinquished"); + assertNotLocked("makeSessionActive"); + + synchronized (mLock) { + if (mRelinquished) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session relinquished"); + } + if (mDestroyed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session destroyed"); + } + if (!mSealed) { + throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, + "Session not sealed"); + } } - if (mDestroyed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed"); + + if (!params.isMultiPackage && needToAskForPermissions()) { + // User needs to confirm installation; + // give installer an intent they can use to involve + // user. + final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); + intent.setPackage(mPm.getPackageInstallerPackageName()); + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendOnUserActionRequired(mContext, statusReceiver, sessionId, intent); + + // Commit was keeping session marked as active until now; release + // that extra refcount so session appears idle. + closeInternal(false); + return null; } - if (!mSealed) { - throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session not sealed"); + + synchronized (mLock) { + return makeSessionActiveLocked(); } + } + @GuardedBy("mLock") + private PackageManagerService.ActiveInstallSession makeSessionActiveLocked() + throws PackageManagerException { if (!params.isMultiPackage) { Objects.requireNonNull(mPackageName); Objects.requireNonNull(mSigningDetails); Objects.requireNonNull(mResolvedBaseFile); - if (needToAskForPermissionsLocked()) { - // User needs to confirm installation; - // give installer an intent they can use to involve - // user. - final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_INSTALL); - intent.setPackage(mPm.getPackageInstallerPackageName()); - intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); - - sendOnUserActionRequired(mContext, mRemoteStatusReceiver, sessionId, intent); - - // Commit was keeping session marked as active until now; release - // that extra refcount so session appears idle. - closeInternal(false); - return null; - } - // Inherit any packages and native libraries from existing install that // haven't been overridden. if (params.mode == SessionParams.MODE_INHERIT_EXISTING) { @@ -2438,7 +2468,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * Determine if creating hard links between source and destination is * possible. That is, do they all live on the same underlying device. */ - private boolean isLinkPossible(List<File> fromFiles, File toDir) { + private static boolean isLinkPossible(List<File> fromFiles, File toDir) { try { final StructStat toStat = Os.stat(toDir.getAbsolutePath()); for (File fromFile : fromFiles) { @@ -2915,7 +2945,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } case IDataLoaderStatusListener.DATA_LOADER_UNAVAILABLE: { // Don't fail or commit the session. Allow caller to commit again. - sendPendingStreaming("DataLoader unavailable"); + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendPendingStreaming(mContext, statusReceiver, sessionId, + "DataLoader unavailable"); break; } case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: @@ -2929,7 +2964,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } catch (RemoteException e) { // In case of streaming failure we don't want to fail or commit the session. // Just return from this method and allow caller to commit again. - sendPendingStreaming(e.getMessage()); + final IntentSender statusReceiver; + synchronized (mLock) { + statusReceiver = mRemoteStatusReceiver; + } + sendPendingStreaming(mContext, statusReceiver, sessionId, e.getMessage()); } } }; @@ -3004,16 +3043,17 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { detailMessage).sendToTarget(); } + @GuardedBy("mLock") + private int[] getChildSessionIdsLocked() { + final int[] childSessionIds = mChildSessionIds.copyKeys(); + return childSessionIds != null ? childSessionIds : EMPTY_CHILD_SESSION_ARRAY; + } + @Override public int[] getChildSessionIds() { - final int[] childSessionIds; synchronized (mLock) { - childSessionIds = mChildSessionIds.copyKeys(); + return getChildSessionIdsLocked(); } - if (childSessionIds != null) { - return childSessionIds; - } - return EMPTY_CHILD_SESSION_ARRAY; } private boolean canBeAddedAsChild(int parentCandidate) { @@ -3323,6 +3363,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { pw.decreaseIndent(); } + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ private static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId, Intent intent) { final Intent fillIn = new Intent(); @@ -3335,6 +3378,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ private static void sendOnPackageInstalled(Context context, IntentSender target, int sessionId, boolean showNotification, int userId, String basePackageName, int returnCode, String msg, Bundle extras) { @@ -3375,13 +3421,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - private void sendPendingStreaming(@Nullable String cause) { - final IntentSender statusReceiver; - synchronized (mLock) { - statusReceiver = mRemoteStatusReceiver; - } - - if (statusReceiver == null) { + /** + * This method doesn't change internal states and is safe to call outside the lock. + */ + private static void sendPendingStreaming(Context context, IntentSender target, int sessionId, + @Nullable String cause) { + if (target == null) { Slog.e(TAG, "Missing receiver for pending streaming status."); return; } @@ -3396,7 +3441,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready"); } try { - statusReceiver.sendIntent(mContext, 0, intent, null, null); + target.sendIntent(context, 0, intent, null, null); } catch (IntentSender.SendIntentException ignored) { } } @@ -3470,10 +3515,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (stageCid != null) { writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid); } - writeBooleanAttribute(out, ATTR_PREPARED, isPrepared()); - writeBooleanAttribute(out, ATTR_COMMITTED, isCommitted()); - writeBooleanAttribute(out, ATTR_DESTROYED, isDestroyed()); - writeBooleanAttribute(out, ATTR_SEALED, isSealed()); + writeBooleanAttribute(out, ATTR_PREPARED, mPrepared); + writeBooleanAttribute(out, ATTR_COMMITTED, mCommitted); + writeBooleanAttribute(out, ATTR_DESTROYED, mDestroyed); + writeBooleanAttribute(out, ATTR_SEALED, mSealed); writeBooleanAttribute(out, ATTR_MULTI_PACKAGE, params.isMultiPackage); writeBooleanAttribute(out, ATTR_STAGED_SESSION, params.isStaged); @@ -3535,7 +3580,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { params.appIconLastModified = appIconFile.lastModified(); } - final int[] childSessionIds = getChildSessionIds(); + final int[] childSessionIds = getChildSessionIdsLocked(); for (int childSessionId : childSessionIds) { out.startTag(null, TAG_CHILD_SESSION); writeIntAttribute(out, ATTR_SESSION_ID, childSessionId); @@ -3543,7 +3588,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } final InstallationFile[] files = getInstallationFilesLocked(); - for (InstallationFile file : getInstallationFilesLocked()) { + for (InstallationFile file : files) { out.startTag(null, TAG_SESSION_FILE); writeIntAttribute(out, ATTR_LOCATION, file.getLocation()); writeStringAttribute(out, ATTR_NAME, file.getName()); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index bd12fd5f5d9a..2854b337fd29 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -651,6 +651,23 @@ public class PackageManagerService extends IPackageManager.Stub private static final long THROW_EXCEPTION_ON_REQUIRE_INSTALL_PACKAGES_TO_ADD_INSTALLER_PACKAGE = 150857253; + /** + * Apps targeting Android S and above need to declare dependencies to the public native + * shared libraries that are defined by the device maker using {@code uses-native-library} tag + * in its {@code AndroidManifest.xml}. + * + * If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist, + * the package manager rejects to install the app. The dependency can be specified as optional + * using {@code android:required} attribute in the tag, in which case failing to satisfy the + * dependency doesn't stop the installation. + * <p>Once installed, an app is provided with only the native shared libraries that are + * specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear + * in the app manifest will fail even if it actually exists on the device. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R) + private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088; + public static final String PLATFORM_PACKAGE_NAME = "android"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; @@ -2952,9 +2969,8 @@ public class PackageManagerService extends IPackageManager.Stub = systemConfig.getSharedLibraries(); final int builtInLibCount = libConfig.size(); for (int i = 0; i < builtInLibCount; i++) { - String name = libConfig.keyAt(i); SystemConfig.SharedLibraryEntry entry = libConfig.valueAt(i); - addBuiltInSharedLibraryLocked(entry.filename, name); + addBuiltInSharedLibraryLocked(libConfig.valueAt(i)); } // Now that we have added all the libraries, iterate again to add dependency @@ -10486,6 +10502,19 @@ public class PackageManagerService extends IPackageManager.Stub null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, existingLibraries, newLibraries); } + // TODO(b/160928779) gate this behavior using ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES + if (pkg.getTargetSdkVersion() > 30) { + if (!pkg.getUsesNativeLibraries().isEmpty() && pkg.getTargetSdkVersion() > 30) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, + null, pkg.getPackageName(), true, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, existingLibraries, newLibraries); + } + if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), + null, null, pkg.getPackageName(), false, pkg.getTargetSdkVersion(), + usesLibraryInfos, availablePackages, existingLibraries, newLibraries); + } + } return usesLibraryInfos; } @@ -12177,15 +12206,16 @@ public class PackageManagerService extends IPackageManager.Stub } @GuardedBy("mLock") - private boolean addBuiltInSharedLibraryLocked(String path, String name) { - if (nonStaticSharedLibExistsLocked(name)) { + private boolean addBuiltInSharedLibraryLocked(SystemConfig.SharedLibraryEntry entry) { + if (nonStaticSharedLibExistsLocked(entry.name)) { return false; } - SharedLibraryInfo libraryInfo = new SharedLibraryInfo(path, null, null, name, - (long) SharedLibraryInfo.VERSION_UNDEFINED, SharedLibraryInfo.TYPE_BUILTIN, - new VersionedPackage(PLATFORM_PACKAGE_NAME, (long) 0), - null, null); + SharedLibraryInfo libraryInfo = new SharedLibraryInfo(entry.filename, null, null, + entry.name, (long) SharedLibraryInfo.VERSION_UNDEFINED, + SharedLibraryInfo.TYPE_BUILTIN, + new VersionedPackage(PLATFORM_PACKAGE_NAME, (long)0), null, null, + entry.isNative); commitSharedLibraryInfoLocked(libraryInfo); return true; @@ -21900,7 +21930,11 @@ public class PackageManagerService extends IPackageManager.Stub pw.print(" -> "); } if (libraryInfo.getPath() != null) { - pw.print(" (jar) "); + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } pw.print(libraryInfo.getPath()); } else { pw.print(" (apk) "); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 13b927e7d9f4..7106499f9b56 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4784,6 +4784,23 @@ public final class Settings { } } + List<String> usesNativeLibraries = pkg.getUsesNativeLibraries(); + if (usesNativeLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesNativeLibraries:"); + for (int i=0; i< usesNativeLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(usesNativeLibraries.get(i)); + } + } + + List<String> usesOptionalNativeLibraries = pkg.getUsesOptionalNativeLibraries(); + if (usesOptionalNativeLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesOptionalNativeLibraries:"); + for (int i=0; i< usesOptionalNativeLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); + pw.println(usesOptionalNativeLibraries.get(i)); + } + } + List<String> usesLibraryFiles = ps.getPkgState().getUsesLibraryFiles(); if (usesLibraryFiles.size() > 0) { pw.print(prefix); pw.println(" usesLibraryFiles:"); diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java index c9e0bb467ce4..39784cf32cea 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackage.java @@ -252,6 +252,19 @@ public interface AndroidPackage extends PkgAppInfo, PkgPackageInfo, ParsingPacka @NonNull List<String> getUsesOptionalLibraries(); + /** @see R.styleabele#AndroidManifestUsesNativeLibrary */ + @NonNull + List<String> getUsesNativeLibraries(); + + /** + * Like {@link #getUsesNativeLibraries()}, but marked optional by setting + * {@link R.styleable#AndroidManifestUsesNativeLibrary_required} to false . Application is + * expected to handle absence manually. + * @see R.styleable#AndroidManifestUsesNativeLibrary + */ + @NonNull + List<String> getUsesOptionalNativeLibraries(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * @see R.styleable#AndroidManifestUsesStaticLibrary diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 2394bafc09de..fd3c1f97df8b 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1469,7 +1469,7 @@ public class TrustManagerService extends SystemService { if (userId > 0) { return userId; } else { - Slog.wtf(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId); + Log.w(TAG, "EXTRA_USER_HANDLE missing or invalid, value=" + userId); return -100; } } diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index f5eed30a19bf..f5e1602ee6be 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -51,6 +51,7 @@ import android.app.AppGlobals; import android.app.GrantedUriPermission; import android.app.IUriGrantsManager; import android.content.ClipData; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; @@ -698,6 +699,11 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { final UriPermission perm = findOrCreateUriPermissionLocked( sourcePkg, targetPkg, targetUid, grantUri); perm.initPersistedModes(modeFlags, createdTime); + mPmInternal.grantImplicitAccess( + targetUserId, null, + UserHandle.getAppId(targetUid), + pi.applicationInfo.uid, + false /* direct */); } } else { Slog.w(TAG, "Persisted grant for " + uri + " had source " + sourcePkg @@ -1171,6 +1177,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { // grant, we can skip generating any bookkeeping; when any advanced // features have been requested, we proceed below to make sure the // provider supports granting permissions + mPmInternal.grantImplicitAccess( + UserHandle.getUserId(targetUid), null, + UserHandle.getAppId(targetUid), pi.applicationInfo.uid, false); return -1; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 55962fc883d9..90f87b16e70d 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1160,9 +1160,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } }; - private Runnable mTryToRebindRunnable = () -> { - tryToRebind(); - }; + private Runnable mTryToRebindRunnable = this::tryToRebind; WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper, int clientUid) { mInfo = info; @@ -1295,14 +1293,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // a short time in the future, specifically to allow any pending package // update message on this same looper thread to be processed. if (!mWallpaper.wallpaperUpdating) { - mContext.getMainThreadHandler().postDelayed(() -> processDisconnect(this), + mContext.getMainThreadHandler().postDelayed(mDisconnectRunnable, 1000); } } } } - public void scheduleTimeoutLocked() { + private void scheduleTimeoutLocked() { // If we didn't reset it right away, do so after we couldn't connect to // it for an extended amount of time to avoid having a black wallpaper. final Handler fgHandler = FgThread.getHandler(); @@ -1342,11 +1340,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void processDisconnect(final ServiceConnection connection) { + private Runnable mDisconnectRunnable = () -> { synchronized (mLock) { // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. - if (connection == mWallpaper.connection) { + if (this == mWallpaper.connection) { final ComponentName wpService = mWallpaper.wallpaperComponent; if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId @@ -1374,7 +1372,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - } + }; /** * Called by a live wallpaper if its colors have changed. @@ -2786,6 +2784,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperConnection.DisplayConnector::disconnectLocked); wallpaper.connection.mService = null; wallpaper.connection.mDisplayConnector.clear(); + + FgThread.getHandler().removeCallbacks(wallpaper.connection.mResetRunnable); + mContext.getMainThreadHandler().removeCallbacks( + wallpaper.connection.mDisconnectRunnable); + mContext.getMainThreadHandler().removeCallbacks( + wallpaper.connection.mTryToRebindRunnable); + wallpaper.connection = null; if (wallpaper == mLastWallpaper) mLastWallpaper = null; } diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 0cd7ffce2ed4..04b1edc3eede 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -1446,7 +1446,9 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { mService.deferWindowLayout(); try { stack.setWindowingMode(WINDOWING_MODE_UNDEFINED); - stack.setBounds(null); + if (stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + stack.setBounds(null); + } toDisplay.getDefaultTaskDisplayArea().positionTaskBehindHome(stack); // Follow on the workaround: activities are kept force hidden till the new windowing diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 751d0c8be24a..e84f040931bb 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1288,11 +1288,11 @@ class ActivityStarter { return false; } // if the realCallingUid is a persistent system process, abort if the IntentSender - // wasn't whitelisted to start an activity + // wasn't allowed to start an activity if (isRealCallingUidPersistentSystemProcess && allowBackgroundActivityStart) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Activity start allowed: realCallingUid (" + realCallingUid - + ") is persistent system process AND intent sender whitelisted " + + ") is persistent system process AND intent sender allowed " + "(allowBackgroundActivityStart = true)"); } return false; @@ -1346,23 +1346,23 @@ class ActivityStarter { // If we don't have callerApp at this point, no caller was provided to startActivity(). // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. If that's the case, we retrieve the WindowProcessController for the send() - // caller, so that we can make the decision based on its foreground/whitelisted state. + // caller, so that we can make the decision based on its state. int callerAppUid = callingUid; if (callerApp == null) { callerApp = mService.getProcessController(realCallingPid, realCallingUid); callerAppUid = realCallingUid; } - // don't abort if the callerApp or other processes of that uid are whitelisted in any way + // don't abort if the callerApp or other processes of that uid are allowed in any way if (callerApp != null) { // first check the original calling process if (callerApp.areBackgroundActivityStartsAllowed()) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: callerApp process (pid = " - + callerApp.getPid() + ", uid = " + callerAppUid + ") is whitelisted"); + + callerApp.getPid() + ", uid = " + callerAppUid + ") is allowed"); } return false; } - // only if that one wasn't whitelisted, check the other ones + // only if that one wasn't allowed, check the other ones final ArraySet<WindowProcessController> uidProcesses = mService.mProcessMap.getProcesses(callerAppUid); if (uidProcesses != null) { @@ -1372,7 +1372,7 @@ class ActivityStarter { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "Background activity start allowed: process " + proc.getPid() - + " from uid " + callerAppUid + " is whitelisted"); + + " from uid " + callerAppUid + " is allowed"); } return false; } @@ -1401,7 +1401,7 @@ class ActivityStarter { + "; isRealCallingUidPersistentSystemProcess: " + isRealCallingUidPersistentSystemProcess + "; originatingPendingIntent: " + originatingPendingIntent - + "; isBgStartWhitelisted: " + allowBackgroundActivityStart + + "; allowBackgroundActivityStart: " + allowBackgroundActivityStart + "; intent: " + intent + "; callerApp: " + callerApp + "]"); diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 127e10a6966b..0b1f4d9e1613 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -490,6 +490,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private ActivityRecord mFixedRotationLaunchingApp; + /** The delay to avoid toggling the animation quickly. */ + private static final long FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS = 250; private FixedRotationAnimationController mFixedRotationAnimationController; final FixedRotationTransitionListener mFixedRotationTransitionListener = @@ -1524,10 +1526,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) { if (mFixedRotationLaunchingApp == null && r != null) { mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation); - if (mFixedRotationAnimationController == null) { - mFixedRotationAnimationController = new FixedRotationAnimationController(this); - mFixedRotationAnimationController.hide(); - } + startFixedRotationAnimation( + // Delay the hide animation to avoid blinking by clicking navigation bar that + // may toggle fixed rotation in a short time. + r == mFixedRotationTransitionListener.mAnimatingRecents /* shouldDebounce */); } else if (mFixedRotationLaunchingApp != null && r == null) { mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this); finishFixedRotationAnimationIfPossible(); @@ -1625,6 +1627,32 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } } + /** + * Starts the hide animation for the windows which will be rotated seamlessly. + * + * @return {@code true} if the animation is executed right now. + */ + private boolean startFixedRotationAnimation(boolean shouldDebounce) { + if (shouldDebounce) { + mWmService.mH.postDelayed(() -> { + synchronized (mWmService.mGlobalLock) { + if (mFixedRotationLaunchingApp != null + && startFixedRotationAnimation(false /* shouldDebounce */)) { + // Apply the transaction so the animation leash can take effect immediately. + getPendingTransaction().apply(); + } + } + }, FIXED_ROTATION_HIDE_ANIMATION_DEBOUNCE_DELAY_MS); + return false; + } + if (mFixedRotationAnimationController == null) { + mFixedRotationAnimationController = new FixedRotationAnimationController(this); + mFixedRotationAnimationController.hide(); + return true; + } + return false; + } + /** Re-show the previously hidden windows if all seamless rotated windows are done. */ void finishFixedRotationAnimationIfPossible() { final FixedRotationAnimationController controller = mFixedRotationAnimationController; @@ -3528,18 +3556,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp == mRemoteInsetsControlTarget)) { return mRemoteInsetsControlTarget; } else { - // Now, a special case -- if the last target's window is in the process of exiting, but - // not removed, keep on the last target to avoid IME flicker. - final WindowState cur = InsetsControlTarget.asWindowOrNull(mInputMethodControlTarget); - if (cur != null && !cur.mRemoved && cur.isDisplayedLw() && cur.isClosing() - && !cur.isActivityTypeHome()) { - if (DEBUG_INPUT_METHOD) { - Slog.v(TAG_WM, "Not changing control while current window" - + " is closing and not removed"); - } - return cur; - } - // Otherwise, we just use the ime target as received from IME. return mInputMethodInputTarget; } } diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java index c7cba77f6797..e2c07491db01 100644 --- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java +++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java @@ -16,8 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; - import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; import static com.android.server.wm.Task.TAG_VISIBILITY; @@ -174,12 +172,7 @@ class EnsureActivitiesVisibleHelper { } final int windowingMode = mContiner.getWindowingMode(); - if (windowingMode == WINDOWING_MODE_FREEFORM) { - // The visibility of tasks and the activities they contain in freeform stack are - // determined individually unlike other stacks where the visibility or fullscreen - // status of an activity in a previous task affects other. - mBehindFullscreenActivity = !mContainerShouldBeVisible; - } else if (!mBehindFullscreenActivity && mContiner.isActivityTypeHome() + if (!mBehindFullscreenActivity && mContiner.isActivityTypeHome() && r.isRootOfTask()) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + mContiner + " stackShouldBeVisible=" + mContainerShouldBeVisible diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 49fafbb3907c..e6e92ea9e489 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5394,12 +5394,9 @@ class Task extends WindowContainer<WindowContainer> { mCurrentUser = userId; super.switchUser(userId); - forAllLeafTasks((t) -> { - if (t.showToCurrentUser() && t != this) { - mChildren.remove(t); - mChildren.add(t); - } - }, true /* traverseTopToBottom */); + if (isLeafTask() && showToCurrentUser()) { + getParent().positionChildAt(POSITION_TOP, this, false /*includeParents*/); + } } void minimalResumeActivityLocked(ActivityRecord r) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 8912d584213e..4c5ac937b988 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -353,7 +353,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } } } else { - throw new RuntimeException("Reparenting leaf Tasks is not supported now."); + throw new RuntimeException("Reparenting leaf Tasks is not supported now. " + task); } } else { // Ugh, of course ActivityStack has its own special reorder logic... diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 9a48154c7770..da9c7f3ea1b5 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -160,7 +160,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private volatile boolean mPerceptible; // Set to true when process was launched with a wrapper attached private volatile boolean mUsingWrapper; - // Set to true if this process is currently temporarily whitelisted to start activities even if + // Set to true if this process is currently temporarily allowed to start activities even if // it's not in the foreground private volatile boolean mAllowBackgroundActivityStarts; // Set of UIDs of clients currently bound to this process @@ -454,7 +454,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } boolean areBackgroundActivityStartsAllowed() { - // allow if the whitelisting flag was explicitly set + // allow if the flag was explicitly set if (mAllowBackgroundActivityStarts) { if (DEBUG_ACTIVITY_STARTS) { Slog.d(TAG, "[WindowProcessController(" + mPid diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 1cbc95090bfd..9ab157210dbd 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -2194,9 +2194,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (dc.mInputMethodInputTarget == this) { dc.setInputMethodInputTarget(null); } - if (dc.mInputMethodControlTarget == this) { - dc.updateImeControlTarget(); - } final int type = mAttrs.type; if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c6b93d6ca4f4..7ec819f13e96 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3570,6 +3570,17 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return new JournaledFile(new File(base), new File(base + ".tmp")); } + /** + * Persist modified values to disk by calling {@link #saveSettingsLocked} for each + * affected user ID. + */ + @GuardedBy("getLockObject()") + private void saveSettingsForUsersLocked(Set<Integer> affectedUserIds) { + for (int userId : affectedUserIds) { + saveSettingsLocked(userId); + } + } + private void saveSettingsLocked(int userHandle) { DevicePolicyData policy = getUserData(userHandle); JournaledFile journal = makeJournaledFile(userHandle); @@ -4785,13 +4796,15 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * Updates a flag that tells us whether the user's password currently satisfies the - * requirements set by all of the user's active admins. The flag is updated both in memory - * and persisted to disk by calling {@link #saveSettingsLocked}, for the value of the flag - * be the correct one upon boot. - * This should be called whenever the password or the admin policies have changed. + * requirements set by all of the user's active admins. + * This should be called whenever the password or the admin policies have changed. The caller + * is responsible for calling {@link #saveSettingsLocked} to persist the change. + * + * @return the set of user IDs that have been affected */ @GuardedBy("getLockObject()") - private void updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) { + private Set<Integer> updatePasswordValidityCheckpointLocked(int userHandle, boolean parent) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); final int credentialOwner = getCredentialOwner(userHandle, parent); DevicePolicyData policy = getUserData(credentialOwner); PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner); @@ -4801,9 +4814,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { metrics, userHandle, parent); if (newCheckpoint != policy.mPasswordValidAtLastCheckpoint) { policy.mPasswordValidAtLastCheckpoint = newCheckpoint; - saveSettingsLocked(credentialOwner); + affectedUserIds.add(credentialOwner); } } + return affectedUserIds; } /** @@ -6175,7 +6189,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private void removeCaApprovalsIfNeeded(int userId) { + private Set<Integer> removeCaApprovalsIfNeeded(int userId) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); for (UserInfo userInfo : mUserManager.getProfiles(userId)) { boolean isSecure = mLockPatternUtils.isSecure(userInfo.id); if (userInfo.isManagedProfile()){ @@ -6184,11 +6199,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (!isSecure) { synchronized (getLockObject()) { getUserData(userInfo.id).mAcceptedCaCertificates.clear(); - saveSettingsLocked(userInfo.id); + affectedUserIds.add(userInfo.id); } mCertificateMonitor.onCertificateApprovalsChanged(userId); } } + return affectedUserIds; } @Override @@ -7458,42 +7474,45 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } DevicePolicyData policy = getUserData(userId); + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); synchronized (getLockObject()) { policy.mFailedPasswordAttempts = 0; - updatePasswordValidityCheckpointLocked(userId, /* parent */ false); - saveSettingsLocked(userId); - updatePasswordExpirationsLocked(userId); + affectedUserIds.add(userId); + affectedUserIds.addAll(updatePasswordValidityCheckpointLocked( + userId, /* parent */ false)); + affectedUserIds.addAll(updatePasswordExpirationsLocked(userId)); setExpirationAlarmCheckLocked(mContext, userId, /* parent */ false); // Send a broadcast to each profile using this password as its primary unlock. sendAdminCommandForLockscreenPoliciesLocked( DeviceAdminReceiver.ACTION_PASSWORD_CHANGED, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, userId); + + affectedUserIds.addAll(removeCaApprovalsIfNeeded(userId)); + saveSettingsForUsersLocked(affectedUserIds); } - removeCaApprovalsIfNeeded(userId); } /** * Called any time the device password is updated. Resets all password expiration clocks. + * + * @return the set of user IDs that have been affected */ - private void updatePasswordExpirationsLocked(int userHandle) { - ArraySet<Integer> affectedUserIds = new ArraySet<Integer>(); + private Set<Integer> updatePasswordExpirationsLocked(int userHandle) { + final ArraySet<Integer> affectedUserIds = new ArraySet<>(); List<ActiveAdmin> admins = getActiveAdminsForLockscreenPoliciesLocked( userHandle, /* parent */ false); - final int N = admins.size(); - for (int i = 0; i < N; i++) { + for (int i = 0; i < admins.size(); i++) { ActiveAdmin admin = admins.get(i); if (admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD)) { affectedUserIds.add(admin.getUserHandle().getIdentifier()); long timeout = admin.passwordExpirationTimeout; - long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; - admin.passwordExpirationDate = expiration; + admin.passwordExpirationDate = + timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L; } } - for (int affectedUserId : affectedUserIds) { - saveSettingsLocked(affectedUserId); - } + return affectedUserIds; } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java index da794da7f9c9..e947e89c4f94 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeAppOpsHelper.java @@ -57,7 +57,7 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean startOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); if (!myAppOp.mAllowed) { return false; @@ -68,20 +68,20 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected void finishOp(int appOp, CallerIdentity callerIdentity) { + public void finishOp(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); Preconditions.checkState(myAppOp.mStarted); myAppOp.mStarted = false; } @Override - protected boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean checkOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); return myAppOp.mAllowed; } @Override - protected boolean noteOp(int appOp, CallerIdentity callerIdentity) { + public boolean noteOp(int appOp, CallerIdentity callerIdentity) { if (!noteOpNoThrow(appOp, callerIdentity)) { throw new SecurityException( "noteOp not allowed for op " + appOp + " and caller " + callerIdentity); @@ -91,7 +91,7 @@ public class FakeAppOpsHelper extends AppOpsHelper { } @Override - protected boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { + public boolean noteOpNoThrow(int appOp, CallerIdentity callerIdentity) { AppOp myAppOp = getOp(callerIdentity.getPackageName(), appOp); if (!myAppOp.mAllowed) { return false; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.java new file mode 100644 index 000000000000..e7d7e310d1e4 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPermissionsHelper.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.location.util; + +import android.location.util.identity.CallerIdentity; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * Version of LocationPermissionsHelper for testing. All permissions are granted unless notified + * otherwise. + */ +public class FakeLocationPermissionsHelper extends LocationPermissionsHelper { + + private final HashMap<String, Set<String>> mRevokedPermissions; + + public FakeLocationPermissionsHelper(AppOpsHelper appOps) { + super(appOps); + mRevokedPermissions = new HashMap<>(); + } + + public void grantPermission(String packageName, String permission) { + getRevokedPermissionsList(packageName).remove(permission); + notifyLocationPermissionsChanged(packageName); + } + + public void revokePermission(String packageName, String permission) { + getRevokedPermissionsList(packageName).add(permission); + notifyLocationPermissionsChanged(packageName); + } + + @Override + protected boolean hasPermission(String permission, CallerIdentity identity) { + return !getRevokedPermissionsList(identity.getPackageName()).contains(permission); + } + + private Set<String> getRevokedPermissionsList(String packageName) { + return mRevokedPermissions.computeIfAbsent(packageName, p -> new HashSet<>()); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java new file mode 100644 index 000000000000..3ead5d4f214d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeLocationPowerSaveModeHelper.java @@ -0,0 +1,49 @@ +/* + * 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.location.util; + +import android.os.IPowerManager; +import android.os.PowerManager.LocationPowerSaveMode; + +/** + * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no + * change". + */ +public class FakeLocationPowerSaveModeHelper extends LocationPowerSaveModeHelper { + + @LocationPowerSaveMode + private int mLocationPowerSaveMode; + + public FakeLocationPowerSaveModeHelper() { + mLocationPowerSaveMode = IPowerManager.LOCATION_MODE_NO_CHANGE; + } + + public void setLocationPowerSaveMode(int locationPowerSaveMode) { + if (locationPowerSaveMode == mLocationPowerSaveMode) { + return; + } + + mLocationPowerSaveMode = locationPowerSaveMode; + notifyLocationPowerSaveModeChanged(locationPowerSaveMode); + } + + @LocationPowerSaveMode + @Override + public int getLocationPowerSaveMode() { + return mLocationPowerSaveMode; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java new file mode 100644 index 000000000000..df697fa1a03c --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeScreenInteractiveHelper.java @@ -0,0 +1,42 @@ +/* + * 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.location.util; + +/** + * Version of ScreenInteractiveHelper for testing. Screen is initialized as interactive (on). + */ +public class FakeScreenInteractiveHelper extends ScreenInteractiveHelper { + + private boolean mIsInteractive; + + public FakeScreenInteractiveHelper() { + mIsInteractive = true; + } + + public void setScreenInteractive(boolean interactive) { + if (interactive == mIsInteractive) { + return; + } + + mIsInteractive = interactive; + notifyScreenInteractiveChanged(interactive); + } + + public boolean isInteractive() { + return mIsInteractive; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java index 726b1b82b699..1d0523f18008 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeSettingsHelper.java @@ -90,7 +90,7 @@ public class FakeSettingsHelper extends SettingsHelper { @Override public boolean isLocationEnabled(int userId) { - return mLocationEnabledSetting.getValue(Boolean.class); + return mLocationEnabledSetting.getValue(userId, Boolean.class); } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java index 336e28c879ba..f5978da416be 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/FakeUserInfoHelper.java @@ -16,7 +16,6 @@ package com.android.server.location.util; -import android.os.Process; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.SparseArray; @@ -27,19 +26,21 @@ import com.android.internal.util.Preconditions; import java.io.FileDescriptor; /** - * Version of UserInfoHelper for testing. The user this code is running under is set as the current - * user by default, with no profiles. + * Version of UserInfoHelper for testing. By default there is one user that starts in a running + * state with a userId of 0; */ public class FakeUserInfoHelper extends UserInfoHelper { + public static final int DEFAULT_USERID = 0; + private final IntArray mRunningUserIds; private final SparseArray<IntArray> mProfiles; private int mCurrentUserId; public FakeUserInfoHelper() { - mCurrentUserId = Process.myUserHandle().getIdentifier(); - mRunningUserIds = IntArray.wrap(new int[]{mCurrentUserId}); + mCurrentUserId = DEFAULT_USERID; + mRunningUserIds = IntArray.wrap(new int[]{DEFAULT_USERID}); mProfiles = new SparseArray<>(); } @@ -67,6 +68,10 @@ public class FakeUserInfoHelper extends UserInfoHelper { dispatchOnUserStopped(userId); } + public void setCurrentUserId(int parentUser) { + setCurrentUserIds(parentUser, new int[]{parentUser}); + } + public void setCurrentUserIds(int parentUser, int[] currentProfileUserIds) { Preconditions.checkArgument(ArrayUtils.contains(currentProfileUserIds, parentUser)); int oldUserId = mCurrentUserId; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java index e6f625217965..4165b6ee111f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/LocationAttributionHelperTest.java @@ -16,7 +16,11 @@ package com.android.server.location.util; +import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; +import static android.app.AppOpsManager.OP_MONITOR_LOCATION; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -46,9 +50,7 @@ public class LocationAttributionHelperTest { public void setUp() { initMocks(this); - when(mAppOpsHelper.startLocationMonitoring(any(CallerIdentity.class))).thenReturn(true); - when(mAppOpsHelper.startHighPowerLocationMonitoring(any(CallerIdentity.class))).thenReturn( - true); + when(mAppOpsHelper.startOpNoThrow(anyInt(), any(CallerIdentity.class))).thenReturn(true); mHelper = new LocationAttributionHelper(mAppOpsHelper); } @@ -63,30 +65,30 @@ public class LocationAttributionHelperTest { Object key4 = new Object(); mHelper.reportLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).stopLocationMonitoring(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1); mHelper.reportLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).stopLocationMonitoring(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); mHelper.reportLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).stopLocationMonitoring(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2); } @Test @@ -99,29 +101,29 @@ public class LocationAttributionHelperTest { Object key4 = new Object(); mHelper.reportHighPowerLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller1); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startHighPowerLocationMonitoring(caller2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); mHelper.reportHighPowerLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); mHelper.reportHighPowerLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).stopHighPowerLocationMonitoring(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java index f40d3168cf98..093aa2e0e771 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemAppOpsHelperTest.java @@ -20,12 +20,8 @@ import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_COARSE_LOCATION; import static android.app.AppOpsManager.OP_FINE_LOCATION; import static android.app.AppOpsManager.OP_MOCK_LOCATION; -import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static android.app.AppOpsManager.OP_MONITOR_LOCATION; -import static com.android.server.location.LocationPermissions.PERMISSION_COARSE; -import static com.android.server.location.LocationPermissions.PERMISSION_FINE; - import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -34,10 +30,12 @@ import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.MockitoAnnotations.initMocks; +import static org.testng.Assert.assertThrows; import android.app.AppOpsManager; import android.content.Context; @@ -105,41 +103,41 @@ public class SystemAppOpsHelperTest { } @Test - public void testCheckLocationAccess() { + public void testCheckOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isTrue(); + assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).checkOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_FINE)).isFalse(); + assertThat(mHelper.checkOpNoThrow(OP_FINE_LOCATION, identity)).isFalse(); identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isTrue(); + assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).checkOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage")); - assertThat(mHelper.checkLocationAccess(identity, PERMISSION_COARSE)).isFalse(); + assertThat(mHelper.checkOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse(); } @Test - public void testNoteLocationAccess() { + public void testNoteOpNoThrow() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isTrue(); + assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOpNoThrow(eq(OP_FINE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_FINE)).isFalse(); + assertThat(mHelper.noteOpNoThrow(OP_FINE_LOCATION, identity)).isFalse(); identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); @@ -147,74 +145,55 @@ public class SystemAppOpsHelperTest { doReturn(MODE_ALLOWED).when( mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isTrue(); + assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOpNoThrow(eq(OP_COARSE_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteLocationAccess(identity, PERMISSION_COARSE)).isFalse(); + assertThat(mHelper.noteOpNoThrow(OP_COARSE_LOCATION, identity)).isFalse(); } @Test - public void testStartLocationMonitoring() { + public void testStartOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"), eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startLocationMonitoring(identity)).isTrue(); + assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).startOpNoThrow(eq(OP_MONITOR_LOCATION), eq(1000), eq("mypackage"), eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startLocationMonitoring(identity)).isFalse(); + assertThat(mHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)).isFalse(); } @Test - public void testStartHighPowerLocationMonitoring() { + public void testFinishOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - doReturn(MODE_ALLOWED).when( - mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000), - eq("mypackage"), - eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isTrue(); - - doReturn(MODE_IGNORED).when( - mAppOps).startOpNoThrow(eq(OP_MONITOR_HIGH_POWER_LOCATION), eq(1000), - eq("mypackage"), - eq(false), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.startHighPowerLocationMonitoring(identity)).isFalse(); - } - - @Test - public void testStopLocationMonitoring() { - CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - - mHelper.stopLocationMonitoring(identity); + mHelper.finishOp(OP_MONITOR_LOCATION, identity); verify(mAppOps).finishOp(OP_MONITOR_LOCATION, 1000, "mypackage", "myfeature"); } @Test - public void testStopHighPowerLocationMonitoring() { - CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); - - mHelper.stopHighPowerLocationMonitoring(identity); - verify(mAppOps).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, 1000, "mypackage", "myfeature"); - } - - @Test - public void testNoteMockLocationAccess() { + public void testNoteOp() { CallerIdentity identity = CallerIdentity.forTest(1000, 1000, "mypackage", "myfeature"); doReturn(MODE_ALLOWED).when( mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteMockLocationAccess(identity)).isTrue(); + assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isTrue(); doReturn(MODE_IGNORED).when( mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), nullable(String.class)); - assertThat(mHelper.noteMockLocationAccess(identity)).isFalse(); + assertThat(mHelper.noteOp(OP_MOCK_LOCATION, identity)).isFalse(); + + + doThrow(new SecurityException()).when( + mAppOps).noteOp(eq(OP_MOCK_LOCATION), eq(1000), eq("mypackage"), eq("myfeature"), + nullable(String.class)); + assertThrows(SecurityException.class, () -> mHelper.noteOp(OP_MOCK_LOCATION, identity)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java new file mode 100644 index 000000000000..2acb70c4b83d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/SystemLocationPowerSaveModeHelperTest.java @@ -0,0 +1,170 @@ +/* + * 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.location.util; + +import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; +import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; +import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; +import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE; +import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.content.Context; +import android.os.PowerManager; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.location.util.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SystemLocationPowerSaveModeHelperTest { + + private static final long TIMEOUT_MS = 5000; + private static final long FAILURE_TIMEOUT_MS = 200; + + @Mock + private PowerManagerInternal mPowerManagerInternal; + + private List<Consumer<PowerSaveState>> mListeners = new ArrayList<>(); + + private SystemLocationPowerSaveModeHelper mHelper; + + @Before + public void setUp() { + initMocks(this); + + LocalServices.addService(PowerManagerInternal.class, mPowerManagerInternal); + + doAnswer(invocation -> mListeners.add(invocation.getArgument(1))).when( + mPowerManagerInternal).registerLowPowerModeObserver(anyInt(), any(Consumer.class)); + + PowerManager powerManager = mock(PowerManager.class); + doReturn(LOCATION_MODE_NO_CHANGE).when(powerManager).getLocationPowerSaveMode(); + Context context = mock(Context.class); + doReturn(powerManager).when(context).getSystemService(PowerManager.class); + + mHelper = new SystemLocationPowerSaveModeHelper(context); + mHelper.onSystemReady(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(PowerManagerInternal.class); + } + + private void sendPowerSaveState(PowerSaveState powerSaveState) { + for (Consumer<PowerSaveState> listener : mListeners) { + listener.accept(powerSaveState); + } + } + + @Test + public void testListener() { + LocationPowerSaveModeChangedListener listener = mock( + LocationPowerSaveModeChangedListener.class); + mHelper.addListener(listener); + + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled( + false).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build()); + verify(listener, after(FAILURE_TIMEOUT_MS).never()).onLocationPowerSaveModeChanged( + anyInt()); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_FOREGROUND_ONLY).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_FOREGROUND_ONLY); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_FOREGROUND_ONLY); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF).setBatterySaverEnabled( + true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo( + LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF); + sendPowerSaveState(new PowerSaveState.Builder().setLocationMode( + LOCATION_MODE_NO_CHANGE).setBatterySaverEnabled(true).build()); + verify(listener, timeout(TIMEOUT_MS)).onLocationPowerSaveModeChanged( + LOCATION_MODE_NO_CHANGE); + assertThat(mHelper.getLocationPowerSaveMode()).isEqualTo(LOCATION_MODE_NO_CHANGE); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java index c22dc104f438..1867be0b9f3b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/util/TestInjector.java @@ -16,22 +16,32 @@ package com.android.server.location.util; +import com.android.server.location.LocationRequestStatistics; + public class TestInjector implements Injector { private final FakeUserInfoHelper mUserInfoHelper; private final FakeAppOpsHelper mAppOpsHelper; + private final FakeLocationPermissionsHelper mLocationPermissionsHelper; private final FakeSettingsHelper mSettingsHelper; private final FakeAppForegroundHelper mAppForegroundHelper; - private final LocationUsageLogger mLocationUsageLogger; + private final FakeLocationPowerSaveModeHelper mLocationPowerSaveModeHelper; + private final FakeScreenInteractiveHelper mScreenInteractiveHelper; private final LocationAttributionHelper mLocationAttributionHelper; + private final LocationUsageLogger mLocationUsageLogger; + private final LocationRequestStatistics mLocationRequestStatistics; public TestInjector() { mUserInfoHelper = new FakeUserInfoHelper(); mAppOpsHelper = new FakeAppOpsHelper(); + mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper); mSettingsHelper = new FakeSettingsHelper(); mAppForegroundHelper = new FakeAppForegroundHelper(); - mLocationUsageLogger = new LocationUsageLogger(); + mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(); + mScreenInteractiveHelper = new FakeScreenInteractiveHelper(); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); + mLocationUsageLogger = new LocationUsageLogger(); + mLocationRequestStatistics = new LocationRequestStatistics(); } @Override @@ -45,6 +55,11 @@ public class TestInjector implements Injector { } @Override + public FakeLocationPermissionsHelper getLocationPermissionsHelper() { + return mLocationPermissionsHelper; + } + + @Override public FakeSettingsHelper getSettingsHelper() { return mSettingsHelper; } @@ -55,12 +70,27 @@ public class TestInjector implements Injector { } @Override - public LocationUsageLogger getLocationUsageLogger() { - return mLocationUsageLogger; + public FakeLocationPowerSaveModeHelper getLocationPowerSaveModeHelper() { + return mLocationPowerSaveModeHelper; + } + + @Override + public FakeScreenInteractiveHelper getScreenInteractiveHelper() { + return mScreenInteractiveHelper; } @Override public LocationAttributionHelper getLocationAttributionHelper() { return mLocationAttributionHelper; } + + @Override + public LocationUsageLogger getLocationUsageLogger() { + return mLocationUsageLogger; + } + + @Override + public LocationRequestStatistics getLocationRequestStatistics() { + return mLocationRequestStatistics; + } } diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java index 4fbc587c8a87..6f37ff5ef329 100644 --- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java @@ -153,6 +153,20 @@ public class GestureLauncherServiceTest { } @Test + public void testIsPanicButtonGestureEnabled_settingDisabled() { + withPanicGestureEnabledSettingValue(false); + assertFalse(mGestureLauncherService.isPanicButtonGestureEnabled( + mContext, FAKE_USER_ID)); + } + + @Test + public void testIsPanicButtonGestureEnabled_settingEnabled() { + withPanicGestureEnabledSettingValue(true); + assertTrue(mGestureLauncherService.isPanicButtonGestureEnabled( + mContext, FAKE_USER_ID)); + } + + @Test public void testHandleCameraLaunchGesture_userSetupComplete() { withUserSetupCompleteValue(true); @@ -882,6 +896,14 @@ public class GestureLauncherServiceTest { UserHandle.USER_CURRENT); } + private void withPanicGestureEnabledSettingValue(boolean enable) { + Settings.Secure.putIntForUser( + mContentResolver, + Settings.Secure.PANIC_GESTURE_ENABLED, + enable ? 1 : 0, + UserHandle.USER_CURRENT); + } + private void withUserSetupCompleteValue(boolean userSetupComplete) { int userSetupCompleteValue = userSetupComplete ? 1 : 0; Settings.Secure.putIntForUser( 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 daaabf8141ff..9a465a91e84e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -48,8 +48,6 @@ import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -4931,20 +4929,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { .thenReturn(passwordMetrics); dpm.reportPasswordChanged(userHandle); - // Drain ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED broadcasts as part of - // reportPasswordChanged() - // This broadcast should be sent 2-4 times: - // * Twice from calls to DevicePolicyManagerService.updatePasswordExpirationsLocked, - // once for each affected user, in DevicePolicyManagerService.reportPasswordChanged. - // * Optionally, at most twice from calls to DevicePolicyManagerService.saveSettingsLocked - // in DevicePolicyManagerService.reportPasswordChanged, once with the userId - // the password change is relevant to and another with the credential owner of said - // userId, if the password checkpoint value changes. - verify(mContext.spiedContext, atMost(4)).sendBroadcastAsUser( - MockUtils.checkIntentAction( - DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), - MockUtils.checkUserHandle(userHandle)); - verify(mContext.spiedContext, atLeast(2)).sendBroadcastAsUser( + verify(mContext.spiedContext, times(1)).sendBroadcastAsUser( MockUtils.checkIntentAction( DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), MockUtils.checkUserHandle(userHandle)); diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index 62b6a65cc6cb..614949c91b9a 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -43,11 +43,19 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.ClipData; import android.content.Intent; import android.content.pm.ProviderInfo; import android.net.Uri; +import android.os.UserHandle; import android.util.ArraySet; import androidx.test.InstrumentationRegistry; @@ -62,6 +70,12 @@ public class UriGrantsManagerServiceTest { private UriGrantsMockContext mContext; private UriGrantsManagerInternal mService; + // we expect the following only during grant if a grant is expected + private void verifyNoVisibilityGrant() { + verify(mContext.mPmInternal, never()) + .grantImplicitAccess(anyInt(), any(), anyInt(), anyInt(), anyBoolean()); + } + @Before public void setUp() throws Exception { mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext()); @@ -83,6 +97,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_PRIMARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** @@ -100,6 +115,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** @@ -111,6 +127,8 @@ public class UriGrantsManagerServiceTest { final NeededUriGrants needed = mService.checkGrantUriPermissionFromIntent( intent, UID_PRIMARY_PUBLIC, PKG_SOCIAL, USER_PRIMARY); assertNull(needed); + verify(mContext.mPmInternal).grantImplicitAccess(eq(USER_PRIMARY), isNull(), eq( + UserHandle.getAppId(UID_PRIMARY_SOCIAL)), eq(UID_PRIMARY_PUBLIC), eq(false)); } /** @@ -128,6 +146,7 @@ public class UriGrantsManagerServiceTest { assertEquals(UID_SECONDARY_SOCIAL, needed.targetUid); assertEquals(FLAG_READ, needed.flags); assertEquals(asSet(expectedGrant), needed.uris); + verifyNoVisibilityGrant(); } /** diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index f2f8a12b6ea9..3772e2500c1b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -618,7 +618,7 @@ public class ActivityStarterTests extends ActivityTestsBase { UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, false, true, false, false, false); runAndVerifyBackgroundActivityStartsSubtest( - "disallowed_callerIsWhitelisted_notAborted", false, + "disallowed_callerIsAllowed_notAborted", false, UNIMPORTANT_UID, false, PROCESS_STATE_TOP + 1, UNIMPORTANT_UID2, false, PROCESS_STATE_TOP + 1, false, false, true, false, false); @@ -639,7 +639,7 @@ public class ActivityStarterTests extends ActivityTestsBase { int callingUid, boolean callingUidHasVisibleWindow, int callingUidProcState, int realCallingUid, boolean realCallingUidHasVisibleWindow, int realCallingUidProcState, boolean hasForegroundActivities, boolean callerIsRecents, - boolean callerIsTempWhitelisted, + boolean callerIsTempAllowed, boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges, boolean isCallingUidDeviceOwner) { // window visibility @@ -664,8 +664,8 @@ public class ActivityStarterTests extends ActivityTestsBase { RecentTasks recentTasks = mock(RecentTasks.class); mService.mStackSupervisor.setRecentTasks(recentTasks); doReturn(callerIsRecents).when(recentTasks).isCallerRecents(callingUid); - // caller is temp whitelisted - callerApp.setAllowBackgroundActivityStarts(callerIsTempWhitelisted); + // caller is temp allowed + callerApp.setAllowBackgroundActivityStarts(callerIsTempAllowed); // caller is instrumenting with background activity starts privileges callerApp.setInstrumenting(callerIsInstrumentingWithBackgroundActivityStartPrivileges, callerIsInstrumentingWithBackgroundActivityStartPrivileges); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index a7e0dd4e674e..96ea64667f6e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -974,26 +974,6 @@ public class DisplayContentTests extends WindowTestsBase { } @Test - public void testComputeImeControlTarget_exitingApp() throws Exception { - final DisplayContent dc = createNewDisplay(); - - WindowState exitingWin = createWindow(null, TYPE_BASE_APPLICATION, "exiting app"); - makeWindowVisible(exitingWin); - exitingWin.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN; - exitingWin.mAnimatingExit = true; - - dc.mInputMethodControlTarget = exitingWin; - dc.mInputMethodTarget = dc.mInputMethodInputTarget = - createWindow(null, TYPE_BASE_APPLICATION, "starting app"); - - assertEquals(exitingWin, dc.computeImeControlTarget()); - - exitingWin.removeImmediately(); - - assertEquals(dc.mInputMethodInputTarget, dc.computeImeControlTarget()); - } - - @Test public void testComputeImeControlTarget_splitscreen() throws Exception { final DisplayContent dc = createNewDisplay(); dc.mInputMethodInputTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 039ffc464337..92b6e6ef8ec9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -186,4 +186,19 @@ public class TaskTests extends WindowTestsBase { assertTrue(r.finishing); }); } + + @Test + public void testSwitchUser() { + final Task rootTask = createTaskStackOnDisplay(mDisplayContent); + final Task childTask = createTaskInStack(rootTask, 0 /* userId */); + final Task leafTask1 = createTaskInStack(childTask, 10 /* userId */); + final Task leafTask2 = createTaskInStack(childTask, 0 /* userId */); + assertEquals(1, rootTask.getChildCount()); + assertEquals(leafTask2, childTask.getTopChild()); + + doReturn(true).when(leafTask1).showToCurrentUser(); + rootTask.switchUser(10); + assertEquals(1, rootTask.getChildCount()); + assertEquals(leafTask1, childTask.getTopChild()); + } } diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml index 597805451b95..61dd9d4cd021 100644 --- a/tests/WindowInsetsTests/AndroidManifest.xml +++ b/tests/WindowInsetsTests/AndroidManifest.xml @@ -16,18 +16,24 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.google.android.test.windowinsetstests"> - - <application android:label="@string/activity_title"> - <activity android:name=".WindowInsetsActivity" - android:theme="@style/appTheme" - android:windowSoftInputMode="adjustResize" - android:exported="true"> + package="com.google.android.test.windowinsetstests"> + <application android:label="@string/application_title"> + <activity android:name=".WindowInsetsTestsMainActivity" + android:exported="true"> <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + + <activity android:name=".ChatActivity" + android:label="@string/chat_activity_title" + android:theme="@style/chat" + android:windowSoftInputMode="adjustResize" /> + + <activity android:name=".ControllerActivity" + android:label="@string/controller_activity_title" + android:theme="@style/controller" /> </application> </manifest> diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/chat_activity.xml index 1b51c4f83fe0..1b51c4f83fe0 100644 --- a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml +++ b/tests/WindowInsetsTests/res/layout/chat_activity.xml diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml new file mode 100644 index 000000000000..d51a4ddd43e8 --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/content" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <Spinner + android:id="@+id/spinnerBehavior" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="20dp" /> + + <ToggleButton + android:id="@+id/toggleButtonStatus" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="true" + android:text="Status Bars Toggle Button" + android:textOff="Status Bars Invisible" + android:textOn="Status Bars Visible" /> + + <SeekBar + android:id="@+id/seekBarStatus" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="20dp" + android:max="10000" + android:progress="10000" /> + + <ToggleButton + android:id="@+id/toggleButtonNavigation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="true" + android:text="Navigation Bars Toggle Button" + android:textOff="Navigation Bars Invisible" + android:textOn="Navigation Bars Visible" /> + + <SeekBar + android:id="@+id/seekBarNavigation" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="20dp" + android:max="10000" + android:progress="10000" /> + + <ToggleButton + android:id="@+id/toggleButtonIme" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checked="true" + android:text="IME Toggle Button" + android:textOff="IME Invisible" + android:textOn="IME Visible" /> + + <SeekBar + android:id="@+id/seekBarIme" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="10dp" + android:layout_marginBottom="20dp" + android:max="10000" + android:progress="0" /> + + <TextView + android:id="@+id/textViewControllableInsets" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="5dp" /> + + <EditText + android:id="@+id/editText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ems="10" + android:hint="For testing IME..." + android:inputType="text" + android:text="" /> + + </LinearLayout> + +</ScrollView>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/res/layout/main_activity.xml b/tests/WindowInsetsTests/res/layout/main_activity.xml new file mode 100644 index 000000000000..621ed89204d1 --- /dev/null +++ b/tests/WindowInsetsTests/res/layout/main_activity.xml @@ -0,0 +1,37 @@ +<?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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <Button + android:id="@+id/chat_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/chat_activity_title" + android:textAllCaps="false"/> + + <Button + android:id="@+id/controller_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/controller_activity_title" + android:textAllCaps="false"/> + +</LinearLayout> diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml index 2b8e5f3da362..1a236c6f751d 100644 --- a/tests/WindowInsetsTests/res/values/strings.xml +++ b/tests/WindowInsetsTests/res/values/strings.xml @@ -16,5 +16,14 @@ --> <resources> - <string name="activity_title">New Insets Chat</string> + <string name="application_title">Window Insets Tests</string> + <string name="chat_activity_title">New Insets Chat</string> + <string name="controller_activity_title">Window Insets Controller</string> + + <!-- The item positions should match the flag values respectively. --> + <string-array name="behaviors"> + <item>BEHAVIOR_SHOW_BARS_BY_TOUCH</item> + <item>BEHAVIOR_SHOW_BARS_BY_SWIPE</item> + <item>BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE</item> + </string-array> </resources> diff --git a/tests/WindowInsetsTests/res/values/styles.xml b/tests/WindowInsetsTests/res/values/styles.xml index 220671fb8e71..a84ffbed600d 100644 --- a/tests/WindowInsetsTests/res/values/styles.xml +++ b/tests/WindowInsetsTests/res/values/styles.xml @@ -17,7 +17,7 @@ <resources> - <style name="appTheme" parent="@style/Theme.MaterialComponents.Light"> + <style name="chat" parent="@style/Theme.MaterialComponents.Light"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> @@ -63,5 +63,11 @@ <dimen name="bubble_padding">8dp</dimen> <dimen name="bubble_padding_side">16dp</dimen> + <style name="controller" parent="android:Theme.Material"> + <item name="android:colorPrimaryDark">#111111</item> + <item name="android:navigationBarColor">#111111</item> + <item name="android:colorPrimary">#222222</item> + <item name="android:colorAccent">#33ccff</item> + </style> </resources>
\ No newline at end of file diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java index 498cb7c1c710..ba12acb2c877 100644 --- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ChatActivity.java @@ -30,7 +30,6 @@ 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; @@ -39,8 +38,6 @@ import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Callback; import android.view.WindowInsetsAnimationControlListener; import android.view.WindowInsetsAnimationController; -import android.view.WindowInsetsController; -import android.view.WindowInsetsController.OnControllableInsetsChangedListener; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; @@ -49,7 +46,7 @@ import java.util.List; import androidx.appcompat.app.AppCompatActivity; -public class WindowInsetsActivity extends AppCompatActivity { +public class ChatActivity extends AppCompatActivity { private View mRoot; @@ -58,7 +55,7 @@ public class WindowInsetsActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.window_inset_activity); + setContentView(R.layout.chat_activity); setSupportActionBar(findViewById(R.id.toolbar)); @@ -71,7 +68,7 @@ public class WindowInsetsActivity extends AppCompatActivity { mRoot.setOnTouchListener(new View.OnTouchListener() { private final ViewConfiguration mViewConfiguration = - ViewConfiguration.get(WindowInsetsActivity.this); + ViewConfiguration.get(ChatActivity.this); WindowInsetsAnimationController mAnimationController; WindowInsetsAnimationControlListener mCurrentRequest; boolean mRequestedController = false; diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java new file mode 100644 index 000000000000..beb4049cb230 --- /dev/null +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java @@ -0,0 +1,202 @@ +/* + * 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.google.android.test.windowinsetstests; + +import android.app.Activity; +import android.graphics.Insets; +import android.os.Bundle; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowInsets.Type; +import android.view.WindowInsetsAnimationControlListener; +import android.view.WindowInsetsAnimationController; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.CompoundButton; +import android.widget.SeekBar; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.ToggleButton; + +public class ControllerActivity extends Activity implements View.OnApplyWindowInsetsListener { + + private ToggleButton mToggleStatus; + private SeekBar mSeekStatus; + private ToggleButton mToggleNavigation; + private SeekBar mSeekNavigation; + private ToggleButton mToggleIme; + private SeekBar mSeekIme; + private TextView mTextControllableInsets; + private boolean[] mNotFromUser = {false}; + private WindowInsets mLastInsets; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.controller_activity); + final Spinner spinnerBehavior = findViewById(R.id.spinnerBehavior); + ArrayAdapter<CharSequence> adapterBehavior = ArrayAdapter.createFromResource(this, + R.array.behaviors, android.R.layout.simple_spinner_item); + adapterBehavior.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerBehavior.setAdapter(adapterBehavior); + spinnerBehavior.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + parent.getWindowInsetsController().setSystemBarsBehavior(position); + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { } + }); + mToggleStatus = findViewById(R.id.toggleButtonStatus); + mToggleStatus.setTag(mNotFromUser); + mToggleStatus.setOnCheckedChangeListener(new ToggleListener(Type.statusBars())); + mSeekStatus = findViewById(R.id.seekBarStatus); + mSeekStatus.setOnSeekBarChangeListener(new SeekBarListener(Type.statusBars())); + mToggleNavigation = findViewById(R.id.toggleButtonNavigation); + mToggleNavigation.setTag(mNotFromUser); + mToggleNavigation.setOnCheckedChangeListener(new ToggleListener(Type.navigationBars())); + mSeekNavigation = findViewById(R.id.seekBarNavigation); + mSeekNavigation.setOnSeekBarChangeListener(new SeekBarListener(Type.navigationBars())); + mToggleIme = findViewById(R.id.toggleButtonIme); + mToggleIme.setTag(mNotFromUser); + mToggleIme.setOnCheckedChangeListener(new ToggleListener(Type.ime())); + mSeekIme = findViewById(R.id.seekBarIme); + mSeekIme.setOnSeekBarChangeListener(new SeekBarListener(Type.ime())); + mTextControllableInsets = findViewById(R.id.textViewControllableInsets); + final View contentView = findViewById(R.id.content); + contentView.setOnApplyWindowInsetsListener(this); + contentView.getWindowInsetsController().addOnControllableInsetsChangedListener( + (c, types) -> mTextControllableInsets.setText("ControllableInsetsTypes=" + types)); + } + + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + mNotFromUser[0] = true; + updateWidgets(insets, Type.statusBars(), mToggleStatus, mSeekStatus); + updateWidgets(insets, Type.navigationBars(), mToggleNavigation, mSeekNavigation); + updateWidgets(insets, Type.ime(), mToggleIme, mSeekIme); + mLastInsets = insets; + mNotFromUser[0] = false; + + // Prevent triggering system gestures while controlling seek bars. + final Insets gestureInsets = insets.getInsets(Type.systemGestures()); + v.setPadding(gestureInsets.left, 0, gestureInsets.right, 0); + + return v.onApplyWindowInsets(insets); + } + + private void updateWidgets(WindowInsets insets, int types, ToggleButton toggle, SeekBar seek) { + final boolean isVisible = insets.isVisible(types); + final boolean wasVisible = mLastInsets != null ? mLastInsets.isVisible(types) : !isVisible; + if (isVisible != wasVisible) { + toggle.setChecked(isVisible); + if (!seek.isPressed()) { + seek.setProgress(isVisible ? seek.getMax() : seek.getMin(), true /* animate*/); + } + } + + } + + private static class ToggleListener implements CompoundButton.OnCheckedChangeListener { + + private final @Type.InsetsType int mTypes; + + ToggleListener(int types) { + mTypes = types; + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (((boolean[]) buttonView.getTag())[0]) { + // not from user + return; + } + if (isChecked) { + buttonView.getWindowInsetsController().show(mTypes); + } else { + buttonView.getWindowInsetsController().hide(mTypes); + } + } + } + + private static class SeekBarListener implements SeekBar.OnSeekBarChangeListener { + + private final @Type.InsetsType int mTypes; + + private WindowInsetsAnimationController mController; + + SeekBarListener(int types) { + mTypes = types; + } + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (mController != null && fromUser) { + final int min = seekBar.getMin(); + final float fraction = (progress - min) / (float) (seekBar.getMax() - min); + final Insets shownInsets = mController.getShownStateInsets(); + final Insets hiddenInsets = mController.getHiddenStateInsets(); + final Insets currentInsets = Insets.of( + (int) (0.5f + fraction * (shownInsets.left - hiddenInsets.left)), + (int) (0.5f + fraction * (shownInsets.top - hiddenInsets.top)), + (int) (0.5f + fraction * (shownInsets.right - hiddenInsets.right)), + (int) (0.5f + fraction * (shownInsets.bottom - hiddenInsets.bottom))); + mController.setInsetsAndAlpha(currentInsets, 1f /* alpha */, fraction); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + if (mController != null) { + return; + } + seekBar.getWindowInsetsController().controlWindowInsetsAnimation(mTypes, + -1 /* durationMs */, null /* interpolator */, null /* cancellationSignal */, + new WindowInsetsAnimationControlListener() { + @Override + public void onReady(WindowInsetsAnimationController controller, int types) { + mController = controller; + if (!seekBar.isPressed()) { + onStopTrackingTouch(seekBar); + } + } + + @Override + public void onFinished(WindowInsetsAnimationController controller) { + mController = null; + } + + @Override + public void onCancelled(WindowInsetsAnimationController controller) { + mController = null; + } + }); + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + final int min = seekBar.getMin(); + final int max = seekBar.getMax(); + final boolean shown = (seekBar.getProgress() - min) * 2 > max - min; + seekBar.setProgress(shown ? max : min); + if (mController != null) { + mController.finish(shown); + } + } + } +} diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.java new file mode 100644 index 000000000000..8b77a78ff51e --- /dev/null +++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsTestsMainActivity.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.google.android.test.windowinsetstests; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +public class WindowInsetsTestsMainActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_activity); + + findViewById(R.id.chat_button).setOnClickListener( + v -> startActivity(new Intent(this, ChatActivity.class))); + + findViewById(R.id.controller_button).setOnClickListener( + v -> startActivity(new Intent(this, ControllerActivity.class))); + } +} diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 32686538c10d..ff54fccda767 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -75,8 +75,10 @@ struct ResourcePathData { }; // Resource file paths are expected to look like: [--/res/]type[-config]/name -static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, const char dir_sep, - std::string* out_error) { +static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, + const char dir_sep, + std::string* out_error, + const CompileOptions& options) { std::vector<std::string> parts = util::Split(path, dir_sep); if (parts.size() < 2) { if (out_error) *out_error = "bad resource path"; @@ -121,7 +123,11 @@ static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, } } - return ResourcePathData{Source(path), dir_str.to_string(), name.to_string(), + const Source res_path = options.source_path + ? StringPiece(options.source_path.value()) + : StringPiece(path); + + return ResourcePathData{res_path, dir_str.to_string(), name.to_string(), extension.to_string(), config_str.to_string(), config}; } @@ -667,7 +673,8 @@ int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* // Extract resource type information from the full path std::string err_str; ResourcePathData path_data; - if (auto maybe_path_data = ExtractResourcePathData(path, inputs->GetDirSeparator(), &err_str)) { + if (auto maybe_path_data = ExtractResourcePathData( + path, inputs->GetDirSeparator(), &err_str, options)) { path_data = maybe_path_data.value(); } else { context->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << err_str); @@ -747,6 +754,11 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage() << "only one of --dir and --zip can be specified"); return 1; + } else if ((options_.res_dir || options_.res_zip) && + options_.source_path && args.size() > 1) { + context.GetDiagnostics()->Error(DiagMessage(kPath) + << "Cannot use an overriding source path with multiple files."); + return 1; } else if (options_.res_dir) { if (!args.empty()) { context.GetDiagnostics()->Error(DiagMessage() << "files given but --dir specified"); diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h index 1752a1adac24..1bc1f6651f85 100644 --- a/tools/aapt2/cmd/Compile.h +++ b/tools/aapt2/cmd/Compile.h @@ -28,6 +28,7 @@ namespace aapt { struct CompileOptions { std::string output_path; + Maybe<std::string> source_path; Maybe<std::string> res_dir; Maybe<std::string> res_zip; Maybe<std::string> generate_text_symbols_path; @@ -69,6 +70,9 @@ class CompileCommand : public Command { AddOptionalSwitch("-v", "Enables verbose logging", &options_.verbose); AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", &trace_folder_); + AddOptionalFlag("--source-path", + "Sets the compiled resource file source file path to the given string.", + &options_.source_path); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index fb786a31360e..0aab94d3299f 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -24,6 +24,7 @@ #include "io/ZipArchive.h" #include "java/AnnotationProcessor.h" #include "test/Test.h" +#include "format/proto/ProtoDeserialize.h" namespace aapt { @@ -253,4 +254,90 @@ TEST_F(CompilerTest, DoNotTranslateTest) { AssertTranslations(this, "donottranslate_foo", expected_not_translatable); } +TEST_F(CompilerTest, RelativePathTest) { + StdErrDiagnostics diag; + const std::string res_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "res"}); + + const std::string path_values_colors = GetTestPath("values/colors.xml"); + WriteFile(path_values_colors, "<resources>" + "<color name=\"color_one\">#008577</color>" + "</resources>"); + + const std::string path_layout_layout_one = GetTestPath("layout/layout_one.xml"); + WriteFile(path_layout_layout_one, "<LinearLayout " + "xmlns:android=\"http://schemas.android.com/apk/res/android\">" + "<TextBox android:id=\"@+id/text_one\" android:background=\"@color/color_one\"/>" + "</LinearLayout>"); + + const std::string compiled_files_dir = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "compiled"}); + CHECK(file::mkdirs(compiled_files_dir.data())); + + const std::string path_values_colors_out = + BuildPath({compiled_files_dir,"values_colors.arsc.flat"}); + const std::string path_layout_layout_one_out = + BuildPath({compiled_files_dir, "layout_layout_one.flat"}); + ::android::base::utf8::unlink(path_values_colors_out.c_str()); + ::android::base::utf8::unlink(path_layout_layout_one_out.c_str()); + const std::string apk_path = BuildPath( + {android::base::Dirname(android::base::GetExecutablePath()), + "integration-tests", "CompileTest", "out.apk"}); + + const std::string source_set_res = BuildPath({"main", "res"}); + const std::string relative_path_values_colors = + BuildPath({source_set_res, "values", "colors.xml"}); + const std::string relative_path_layout_layout_one = + BuildPath({source_set_res, "layout", "layout_one.xml"}); + + CompileCommand(&diag).Execute({ + path_values_colors, + "-o", + compiled_files_dir, + "--source-path", + relative_path_values_colors}, + &std::cerr); + + CompileCommand(&diag).Execute({ + path_layout_layout_one, + "-o", + compiled_files_dir, + "--source-path", + relative_path_layout_layout_one}, + &std::cerr); + + std::ifstream ifs_values(path_values_colors_out); + std::string content_values((std::istreambuf_iterator<char>(ifs_values)), + (std::istreambuf_iterator<char>())); + ASSERT_NE(content_values.find(relative_path_values_colors), -1); + ASSERT_EQ(content_values.find(path_values_colors), -1); + + Link({"-o", apk_path, "--manifest", GetDefaultManifest(), "--proto-format"}, + compiled_files_dir, &diag); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, &diag); + ResourceTable* resource_table = apk.get()->GetResourceTable(); + const std::vector<std::unique_ptr<StringPool::Entry>>& pool_strings = + resource_table->string_pool.strings(); + + ASSERT_EQ(pool_strings.size(), 2); + ASSERT_EQ(pool_strings[0]->value, "res/layout/layout_one.xml"); + ASSERT_EQ(pool_strings[1]->value, "res/layout-v1/layout_one.xml"); + + // Check resources.pb contains relative sources. + io::IFile* proto_file = + apk.get()->GetFileCollection()->FindFile("resources.pb"); + std::unique_ptr<io::InputStream> proto_stream = proto_file->OpenInputStream(); + io::ProtoInputStreamReader proto_reader(proto_stream.get()); + pb::ResourceTable pb_table; + proto_reader.ReadMessage(&pb_table); + + const std::string pool_strings_proto = pb_table.source_pool().data(); + + ASSERT_NE(pool_strings_proto.find(relative_path_values_colors), -1); + ASSERT_NE(pool_strings_proto.find(relative_path_layout_layout_one), -1); +} + } // namespace aapt diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 4a6bfd031284..53d9ffe21949 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -1405,6 +1405,29 @@ class UsesStaticLibrary : public ManifestExtractor::Element { } }; +/** Represents <uses-native-library> elements. **/ +class UsesNativeLibrary : public ManifestExtractor::Element { + public: + UsesNativeLibrary() = default; + std::string name; + int required; + + void Extract(xml::Element* element) override { + auto parent_stack = extractor()->parent_stack(); + if (parent_stack.size() > 0 && ElementCast<Application>(parent_stack[0])) { + name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), ""); + required = GetAttributeIntegerDefault(FindAttribute(element, REQUIRED_ATTR), 1); + } + } + + void Print(text::Printer* printer) override { + if (!name.empty()) { + printer->Print(StringPrintf("uses-native-library%s:'%s'\n", + (required == 0) ? "-not-required" : "", name.data())); + } + } +}; + /** * Represents <meta-data> elements. These tags are only printed when a flag is passed in to * explicitly enable meta data printing. @@ -2245,6 +2268,7 @@ T* ElementCast(ManifestExtractor::Element* element) { {"uses-static-library", std::is_base_of<UsesStaticLibrary, T>::value}, {"additional-certificate", std::is_base_of<AdditionalCertificate, T>::value}, {"uses-sdk", std::is_base_of<UsesSdkBadging, T>::value}, + {"uses-native-library", std::is_base_of<UsesNativeLibrary, T>::value}, }; auto check = kTagCheck.find(element->tag()); @@ -2295,6 +2319,7 @@ std::unique_ptr<ManifestExtractor::Element> ManifestExtractor::Element::Inflate( {"uses-package", &CreateType<UsesPackage>}, {"additional-certificate", &CreateType<AdditionalCertificate>}, {"uses-sdk", &CreateType<UsesSdkBadging>}, + {"uses-native-library", &CreateType<UsesNativeLibrary>}, }; // Attempt to map the xml tag to a element inflater diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 3d69093a936a..49f8e1bcd30b 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -422,6 +422,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action.Action(OptionalNameIsJavaClassName); application_action["uses-library"].Action(RequiredNameIsNotEmpty); + application_action["uses-native-library"].Action(RequiredNameIsNotEmpty); application_action["library"].Action(RequiredNameIsNotEmpty); application_action["profileable"]; |