diff options
281 files changed, 5392 insertions, 2829 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index ab0a8adb5daf..55e681521048 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -489,14 +489,6 @@ public class AppStandbyController | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - /** - * Whether we should allow apps into the - * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket or not. - * If false, any attempts to put an app into the bucket will put the app into the - * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE} bucket instead. - */ - private boolean mAllowRestrictedBucket; - private volatile boolean mAppIdleEnabled; private volatile boolean mIsCharging; private boolean mSystemServicesReady = false; @@ -1058,13 +1050,6 @@ public class AppStandbyController Slog.d(TAG, "Bringing down to RESTRICTED due to timeout"); } } - if (newBucket == STANDBY_BUCKET_RESTRICTED && !mAllowRestrictedBucket) { - newBucket = STANDBY_BUCKET_RARE; - // Leave the reason alone. - if (DEBUG) { - Slog.d(TAG, "Bringing up from RESTRICTED to RARE due to off switch"); - } - } if (newBucket > minBucket) { newBucket = minBucket; // Leave the reason alone. @@ -1689,7 +1674,7 @@ public class AppStandbyController final int reason = (REASON_MAIN_MASK & mainReason) | (REASON_SUB_MASK & restrictReason); final long nowElapsed = mInjector.elapsedRealtime(); - final int bucket = mAllowRestrictedBucket ? STANDBY_BUCKET_RESTRICTED : STANDBY_BUCKET_RARE; + final int bucket = STANDBY_BUCKET_RESTRICTED; setAppStandbyBucket(packageName, userId, bucket, reason, nowElapsed, false); } @@ -1793,9 +1778,6 @@ public class AppStandbyController Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName); return; } - if (newBucket == STANDBY_BUCKET_RESTRICTED && !mAllowRestrictedBucket) { - newBucket = STANDBY_BUCKET_RARE; - } AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, userId, elapsedRealtime); boolean predicted = (reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED; @@ -1921,7 +1903,6 @@ public class AppStandbyController + " due to min timeout"); } } else if (newBucket == STANDBY_BUCKET_RARE - && mAllowRestrictedBucket && getBucketForLocked(packageName, userId, elapsedRealtime) == STANDBY_BUCKET_RESTRICTED) { // Prediction doesn't think the app will be used anytime soon and @@ -2529,8 +2510,6 @@ public class AppStandbyController pw.println(); pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled); - pw.print(" mAllowRestrictedBucket="); - pw.print(mAllowRestrictedBucket); pw.print(" mIsCharging="); pw.print(mIsCharging); pw.println(); @@ -2711,12 +2690,6 @@ public class AppStandbyController } } - boolean isRestrictedBucketEnabled() { - return Global.getInt(mContext.getContentResolver(), - Global.ENABLE_RESTRICTED_BUCKET, - Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1; - } - File getDataSystemDirectory() { return Environment.getDataSystemDirectory(); } @@ -3079,11 +3052,6 @@ public class AppStandbyController // APP_STANDBY_ENABLED is a SystemApi that some apps may be watching, so best to // leave it in Settings. cr.registerContentObserver(Global.getUriFor(Global.APP_STANDBY_ENABLED), false, this); - // Leave ENABLE_RESTRICTED_BUCKET as a user-controlled setting which will stay in - // Settings. - // TODO: make setting user-specific - cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET), - false, this); // ADAPTIVE_BATTERY_MANAGEMENT_ENABLED is a user setting, so it has to stay in Settings. cr.registerContentObserver(Global.getUriFor(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED), false, this); @@ -3284,10 +3252,6 @@ public class AppStandbyController Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED)); } - synchronized (mAppIdleLock) { - mAllowRestrictedBucket = mInjector.isRestrictedBucketEnabled(); - } - setAppIdleEnabled(mInjector.isAppIdleEnabled()); } diff --git a/api/Android.bp b/api/Android.bp index 73dbd286229d..24b30048fab7 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -41,23 +41,6 @@ bootstrap_go_package { } python_binary_host { - name: "api_versions_trimmer", - srcs: ["api_versions_trimmer.py"], -} - -python_test_host { - name: "api_versions_trimmer_unittests", - main: "api_versions_trimmer_unittests.py", - srcs: [ - "api_versions_trimmer_unittests.py", - "api_versions_trimmer.py", - ], - test_options: { - unit_test: true, - }, -} - -python_binary_host { name: "merge_annotation_zips", srcs: ["merge_annotation_zips.py"], } diff --git a/api/api.go b/api/api.go index 9876abb8ce36..09c238336a39 100644 --- a/api/api.go +++ b/api/api.go @@ -194,55 +194,6 @@ func createMergedAnnotationsFilegroups(ctx android.LoadHookContext, modules, sys } } -func createFilteredApiVersions(ctx android.LoadHookContext, modules []string) { - // For the filtered api versions, we prune all APIs except art module's APIs. because - // 1) ART apis are available by default to all modules, while other module-to-module deps are - // explicit and probably receive more scrutiny anyway - // 2) The number of ART/libcore APIs is large, so not linting them would create a large gap - // 3) It's a compromise. Ideally we wouldn't be filtering out any module APIs, and have - // per-module lint databases that excludes just that module's APIs. Alas, that's more - // difficult to achieve. - modules = remove(modules, art) - - for _, i := range []struct{ - name string - out string - in string - }{ - { - // We shouldn't need public-filtered or system-filtered. - // public-filtered is currently used to lint things that - // use the module sdk or the system server sdk, but those - // should be switched over to module-filtered and - // system-server-filtered, and then public-filtered can - // be removed. - name: "api-versions-xml-public-filtered", - out: "api-versions-public-filtered.xml", - in: ":api_versions_public{.api_versions.xml}", - }, { - name: "api-versions-xml-module-lib-filtered", - out: "api-versions-module-lib-filtered.xml", - in: ":api_versions_module_lib{.api_versions.xml}", - }, { - name: "api-versions-xml-system-server-filtered", - out: "api-versions-system-server-filtered.xml", - in: ":api_versions_system_server{.api_versions.xml}", - }, - } { - props := genruleProps{} - props.Name = proptools.StringPtr(i.name) - props.Out = []string{i.out} - // Note: order matters: first parameter is the full api-versions.xml - // after that the stubs files in any order - // stubs files are all modules that export API surfaces EXCEPT ART - props.Srcs = append([]string{i.in}, createSrcs(modules, ".stubs{.jar}")...) - props.Tools = []string{"api_versions_trimmer"} - props.Cmd = proptools.StringPtr("$(location api_versions_trimmer) $(out) $(in)") - props.Dists = []android.Dist{{Targets: []string{"sdk"}}} - ctx.CreateModule(genrule.GenRuleFactory, &props, &bp2buildNotAvailable) - } -} - func createMergedPublicStubs(ctx android.LoadHookContext, modules []string) { props := libraryProps{} props.Name = proptools.StringPtr("all-modules-public-stubs") @@ -395,8 +346,6 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath) - createFilteredApiVersions(ctx, bootclasspath) - createPublicStubsSourceFilegroup(ctx, bootclasspath) } diff --git a/api/api_versions_trimmer.py b/api/api_versions_trimmer.py deleted file mode 100755 index 9afd95a3003a..000000000000 --- a/api/api_versions_trimmer.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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. - -"""Script to remove mainline APIs from the api-versions.xml.""" - -import argparse -import re -import xml.etree.ElementTree as ET -import zipfile - - -def read_classes(stubs): - """Read classes from the stubs file. - - Args: - stubs: argument can be a path to a file (a string), a file-like object or a - path-like object - - Returns: - a set of the classes found in the file (set of strings) - """ - classes = set() - with zipfile.ZipFile(stubs) as z: - for info in z.infolist(): - if (not info.is_dir() - and info.filename.endswith(".class") - and not info.filename.startswith("META-INF")): - # drop ".class" extension - classes.add(info.filename[:-6]) - return classes - - -def filter_method_tag(method, classes_to_remove): - """Updates the signature of this method by calling filter_method_signature. - - Updates the method passed into this function. - - Args: - method: xml element that represents a method - classes_to_remove: set of classes you to remove - """ - filtered = filter_method_signature(method.get("name"), classes_to_remove) - method.set("name", filtered) - - -def filter_method_signature(signature, classes_to_remove): - """Removes mentions of certain classes from this method signature. - - Replaces any existing classes that need to be removed, with java/lang/Object - - Args: - signature: string that is a java representation of a method signature - classes_to_remove: set of classes you to remove - """ - regex = re.compile("L.*?;") - start = signature.find("(") - matches = set(regex.findall(signature[start:])) - for m in matches: - # m[1:-1] to drop the leading `L` and `;` ending - if m[1:-1] in classes_to_remove: - signature = signature.replace(m, "Ljava/lang/Object;") - return signature - - -def filter_lint_database(database, classes_to_remove, output): - """Reads a lint database and writes a filtered version without some classes. - - Reads database from api-versions.xml and removes any references to classes - in the second argument. Writes the result (another xml with the same format - of the database) to output. - - Args: - database: path to xml with lint database to read - classes_to_remove: iterable (ideally a set or similar for quick - lookups) that enumerates the classes that should be removed - output: path to write the filtered database - """ - xml = ET.parse(database) - root = xml.getroot() - for c in xml.findall("class"): - cname = c.get("name") - if cname in classes_to_remove: - root.remove(c) - else: - # find the <extends /> tag inside this class to see if the parent - # has been removed from the known classes (attribute called name) - super_classes = c.findall("extends") - for super_class in super_classes: - super_class_name = super_class.get("name") - if super_class_name in classes_to_remove: - super_class.set("name", "java/lang/Object") - interfaces = c.findall("implements") - for interface in interfaces: - interface_name = interface.get("name") - if interface_name in classes_to_remove: - c.remove(interface) - for method in c.findall("method"): - filter_method_tag(method, classes_to_remove) - xml.write(output) - - -def main(): - """Run the program.""" - parser = argparse.ArgumentParser( - description= - ("Read a lint database (api-versions.xml) and many stubs jar files. " - "Produce another database file that doesn't include the classes present " - "in the stubs file(s).")) - parser.add_argument("output", help="Destination of the result (xml file).") - parser.add_argument( - "api_versions", - help="The lint database (api-versions.xml file) to read data from" - ) - parser.add_argument("stubs", nargs="+", help="The stubs jar file(s)") - parsed = parser.parse_args() - classes = set() - for stub in parsed.stubs: - classes.update(read_classes(stub)) - filter_lint_database(parsed.api_versions, classes, parsed.output) - - -if __name__ == "__main__": - main() diff --git a/api/api_versions_trimmer_unittests.py b/api/api_versions_trimmer_unittests.py deleted file mode 100644 index d2e5b7d1a07e..000000000000 --- a/api/api_versions_trimmer_unittests.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2021 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. - -import io -import re -import unittest -import xml.etree.ElementTree as ET -import zipfile - -import api_versions_trimmer - - -def create_in_memory_zip_file(files): - f = io.BytesIO() - with zipfile.ZipFile(f, "w") as z: - for fname in files: - with z.open(fname, mode="w") as class_file: - class_file.write(b"") - return f - - -def indent(elem, level=0): - i = "\n" + level * " " - j = "\n" + (level - 1) * " " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for subelem in elem: - indent(subelem, level + 1) - if not elem.tail or not elem.tail.strip(): - elem.tail = j - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = j - return elem - - -def pretty_print(s): - tree = ET.parse(io.StringIO(s)) - el = indent(tree.getroot()) - res = ET.tostring(el).decode("utf-8") - # remove empty lines inside the result because this still breaks some - # comparisons - return re.sub(r"\n\s*\n", "\n", res, re.MULTILINE) - - -class ApiVersionsTrimmerUnittests(unittest.TestCase): - - def setUp(self): - # so it prints diffs in long strings (xml files) - self.maxDiff = None - - def test_read_classes(self): - f = create_in_memory_zip_file( - ["a/b/C.class", - "a/b/D.class", - ] - ) - res = api_versions_trimmer.read_classes(f) - self.assertEqual({"a/b/C", "a/b/D"}, res) - - def test_read_classes_ignore_dex(self): - f = create_in_memory_zip_file( - ["a/b/C.class", - "a/b/D.class", - "a/b/E.dex", - "f.dex", - ] - ) - res = api_versions_trimmer.read_classes(f) - self.assertEqual({"a/b/C", "a/b/D"}, res) - - def test_read_classes_ignore_manifest(self): - f = create_in_memory_zip_file( - ["a/b/C.class", - "a/b/D.class", - "META-INFO/G.class" - ] - ) - res = api_versions_trimmer.read_classes(f) - self.assertEqual({"a/b/C", "a/b/D"}, res) - - def test_filter_method_signature(self): - xml = """ - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> - """ - method = ET.fromstring(xml) - classes_to_remove = {"android/accessibilityservice/GestureDescription"} - expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" - api_versions_trimmer.filter_method_tag(method, classes_to_remove) - self.assertEqual(expected, method.get("name")) - - def test_filter_method_signature_with_L_in_method(self): - xml = """ - <method name="dispatchLeftGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> - """ - method = ET.fromstring(xml) - classes_to_remove = {"android/accessibilityservice/GestureDescription"} - expected = "dispatchLeftGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" - api_versions_trimmer.filter_method_tag(method, classes_to_remove) - self.assertEqual(expected, method.get("name")) - - def test_filter_method_signature_with_L_in_class(self): - xml = """ - <method name="dispatchGesture(Landroid/accessibilityservice/LeftGestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> - """ - method = ET.fromstring(xml) - classes_to_remove = {"android/accessibilityservice/LeftGestureDescription"} - expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" - api_versions_trimmer.filter_method_tag(method, classes_to_remove) - self.assertEqual(expected, method.get("name")) - - def test_filter_method_signature_with_inner_class(self): - xml = """ - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription$Inner;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" since="24"/> - """ - method = ET.fromstring(xml) - classes_to_remove = {"android/accessibilityservice/GestureDescription$Inner"} - expected = "dispatchGesture(Ljava/lang/Object;Landroid/accessibilityservice/AccessibilityService$GestureResultCallback;Landroid/os/Handler;)Z" - api_versions_trimmer.filter_method_tag(method, classes_to_remove) - self.assertEqual(expected, method.get("name")) - - def _run_filter_db_test(self, database_str, expected): - """Performs the pattern of testing the filter_lint_database method. - - Filters instances of the class "a/b/C" (hard-coded) from the database string - and compares the result with the expected result (performs formatting of - the xml of both inputs) - - Args: - database_str: string, the contents of the lint database (api-versions.xml) - expected: string, the expected result after filtering the original - database - """ - database = io.StringIO(database_str) - classes_to_remove = {"a/b/C"} - output = io.BytesIO() - api_versions_trimmer.filter_lint_database( - database, - classes_to_remove, - output - ) - expected = pretty_print(expected) - res = pretty_print(output.getvalue().decode("utf-8")) - self.assertEqual(expected, res) - - def test_filter_lint_database_updates_method_signature_params(self): - self._run_filter_db_test( - database_str=""" - <api version="2"> - <!-- will be removed --> - <class name="a/b/C" since="1"> - <extends name="java/lang/Object"/> - </class> - - <class name="a/b/E" since="1"> - <!-- extends will be modified --> - <extends name="a/b/C"/> - <!-- first parameter will be modified --> - <method name="dispatchGesture(La/b/C;Landroid/os/Handler;)Z" since="24"/> - <!-- second should remain untouched --> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """, - expected=""" - <api version="2"> - <class name="a/b/E" since="1"> - <extends name="java/lang/Object"/> - <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """) - - def test_filter_lint_database_updates_method_signature_return(self): - self._run_filter_db_test( - database_str=""" - <api version="2"> - <!-- will be removed --> - <class name="a/b/C" since="1"> - <extends name="java/lang/Object"/> - </class> - - <class name="a/b/E" since="1"> - <!-- extends will be modified --> - <extends name="a/b/C"/> - <!-- return type should be changed --> - <method name="gestureIdToString(I)La/b/C;" since="24"/> - </class> - </api> - """, - expected=""" - <api version="2"> - <class name="a/b/E" since="1"> - - <extends name="java/lang/Object"/> - - <method name="gestureIdToString(I)Ljava/lang/Object;" since="24"/> - </class> - </api> - """) - - def test_filter_lint_database_removes_implements(self): - self._run_filter_db_test( - database_str=""" - <api version="2"> - <!-- will be removed --> - <class name="a/b/C" since="1"> - <extends name="java/lang/Object"/> - </class> - - <class name="a/b/D" since="1"> - <extends name="java/lang/Object"/> - <implements name="a/b/C"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """, - expected=""" - <api version="2"> - - <class name="a/b/D" since="1"> - <extends name="java/lang/Object"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """) - - def test_filter_lint_database_updates_extends(self): - self._run_filter_db_test( - database_str=""" - <api version="2"> - <!-- will be removed --> - <class name="a/b/C" since="1"> - <extends name="java/lang/Object"/> - </class> - - <class name="a/b/E" since="1"> - <!-- extends will be modified --> - <extends name="a/b/C"/> - <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """, - expected=""" - <api version="2"> - <class name="a/b/E" since="1"> - <extends name="java/lang/Object"/> - <method name="dispatchGesture(Ljava/lang/Object;Landroid/os/Handler;)Z" since="24"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """) - - def test_filter_lint_database_removes_class(self): - self._run_filter_db_test( - database_str=""" - <api version="2"> - <!-- will be removed --> - <class name="a/b/C" since="1"> - <extends name="java/lang/Object"/> - </class> - - <class name="a/b/D" since="1"> - <extends name="java/lang/Object"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """, - expected=""" - <api version="2"> - - <class name="a/b/D" since="1"> - <extends name="java/lang/Object"/> - <method name="dispatchGesture(Landroid/accessibilityservice/GestureDescription;Landroid/accessibilityservice/AccessibilityService$GestureRe -sultCallback;Landroid/os/Handler;)Z" since="24"/> - </class> - </api> - """) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 00d9a4bf05f0..c4e8b0e49af3 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -458,6 +458,7 @@ public: EGLConfig BootAnimation::getEglConfig(const EGLDisplay& display) { const EGLint attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, diff --git a/cmds/svc/src/com/android/commands/svc/PowerCommand.java b/cmds/svc/src/com/android/commands/svc/PowerCommand.java index 957ebfbef799..a7560b23c6bd 100644 --- a/cmds/svc/src/com/android/commands/svc/PowerCommand.java +++ b/cmds/svc/src/com/android/commands/svc/PowerCommand.java @@ -40,7 +40,7 @@ public class PowerCommand extends Svc.Command { public String longHelp() { return shortHelp() + "\n" + "\n" - + "usage: svc power stayon [true|false|usb|ac|wireless]\n" + + "usage: svc power stayon [true|false|usb|ac|wireless|dock]\n" + " Set the 'keep awake while plugged in' setting.\n" + " svc power reboot [reason]\n" + " Perform a runtime shutdown and reboot device with specified reason.\n" @@ -66,9 +66,10 @@ public class PowerCommand extends Svc.Command { if ("stayon".equals(args[1]) && args.length == 3) { int val; if ("true".equals(args[2])) { - val = BatteryManager.BATTERY_PLUGGED_AC | - BatteryManager.BATTERY_PLUGGED_USB | - BatteryManager.BATTERY_PLUGGED_WIRELESS; + val = BatteryManager.BATTERY_PLUGGED_AC + | BatteryManager.BATTERY_PLUGGED_USB + | BatteryManager.BATTERY_PLUGGED_WIRELESS + | BatteryManager.BATTERY_PLUGGED_DOCK; } else if ("false".equals(args[2])) { val = 0; @@ -78,6 +79,8 @@ public class PowerCommand extends Svc.Command { val = BatteryManager.BATTERY_PLUGGED_AC; } else if ("wireless".equals(args[2])) { val = BatteryManager.BATTERY_PLUGGED_WIRELESS; + } else if ("dock".equals(args[2])) { + val = BatteryManager.BATTERY_PLUGGED_DOCK; } else { break fail; } diff --git a/core/api/current.txt b/core/api/current.txt index 06e3160eb107..70dcd542d715 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -83,6 +83,7 @@ package android { field public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE"; field public static final String CONFIGURE_WIFI_DISPLAY = "android.permission.CONFIGURE_WIFI_DISPLAY"; field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES"; + field public static final String CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS = "android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS"; field public static final String CREDENTIAL_MANAGER_SET_ORIGIN = "android.permission.CREDENTIAL_MANAGER_SET_ORIGIN"; field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES"; field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES"; @@ -556,6 +557,7 @@ package android { field public static final int canTakeScreenshot = 16844303; // 0x101060f field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final int cantSaveState = 16844142; // 0x101056e + field public static final int capability; field @Deprecated public static final int capitalize = 16843113; // 0x1010169 field public static final int category = 16843752; // 0x10103e8 field public static final int centerBright = 16842956; // 0x10100cc @@ -1448,6 +1450,7 @@ package android { field public static final int sessionService = 16843837; // 0x101043d field public static final int settingsActivity = 16843301; // 0x1010225 field public static final int settingsSliceUri = 16844179; // 0x1010593 + field public static final int settingsSubtitle; field public static final int setupActivity = 16843766; // 0x10103f6 field public static final int shadowColor = 16843105; // 0x1010161 field public static final int shadowDx = 16843106; // 0x1010162 @@ -13672,8 +13675,9 @@ package android.credentials { } public final class CredentialOption implements android.os.Parcelable { - ctor public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean); + ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean); method public int describeContents(); + method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders(); method @NonNull public android.os.Bundle getCandidateQueryData(); method @NonNull public android.os.Bundle getCredentialRetrievalData(); method @NonNull public String getType(); @@ -13683,6 +13687,14 @@ package android.credentials { field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING"; } + public static final class CredentialOption.Builder { + ctor public CredentialOption.Builder(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle); + method @NonNull public android.credentials.CredentialOption.Builder addAllowedProvider(@NonNull android.content.ComponentName); + method @NonNull public android.credentials.CredentialOption build(); + method @NonNull public android.credentials.CredentialOption.Builder setAllowedProviders(@NonNull java.util.Set<android.content.ComponentName>); + method @NonNull public android.credentials.CredentialOption.Builder setIsSystemProviderRequired(boolean); + } + public class GetCredentialException extends java.lang.Exception { ctor public GetCredentialException(@NonNull String, @Nullable String); ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable); @@ -40707,7 +40719,7 @@ package android.service.credentials { method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>); method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>); - field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities"; + field @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities"; field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST"; field public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE"; field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION"; @@ -40717,6 +40729,7 @@ package android.service.credentials { field public static final String EXTRA_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.GET_CREDENTIAL_REQUEST"; field public static final String EXTRA_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE"; field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService"; + field public static final String SERVICE_META_DATA = "android.credentials.provider"; } public final class GetCredentialRequest implements android.os.Parcelable { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index b7d97eae56aa..51863bba480a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1112,6 +1112,7 @@ package android.credentials { method @Nullable public CharSequence getLabel(@NonNull android.content.Context); method @Nullable public android.graphics.drawable.Drawable getServiceIcon(@NonNull android.content.Context); method @NonNull public android.content.pm.ServiceInfo getServiceInfo(); + method @Nullable public CharSequence getSettingsSubtitle(); method @NonNull public boolean hasCapability(@NonNull String); method public boolean isEnabled(); method public boolean isSystemProvider(); @@ -1124,6 +1125,7 @@ package android.credentials { method @NonNull public android.credentials.CredentialProviderInfo.Builder addCapabilities(@NonNull java.util.List<java.lang.String>); method @NonNull public android.credentials.CredentialProviderInfo build(); method @NonNull public android.credentials.CredentialProviderInfo.Builder setEnabled(boolean); + method @NonNull public android.credentials.CredentialProviderInfo.Builder setSettingsSubtitle(@Nullable CharSequence); method @NonNull public android.credentials.CredentialProviderInfo.Builder setSystemProvider(boolean); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 502ef0db586b..99de72480110 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -3269,6 +3269,43 @@ public class Notification implements Parcelable /** * @hide */ + public static boolean areIconsDifferent(Notification first, Notification second) { + return areIconsMaybeDifferent(first.getSmallIcon(), second.getSmallIcon()) + || areIconsMaybeDifferent(first.getLargeIcon(), second.getLargeIcon()); + } + + /** + * Note that we aren't actually comparing the contents of the bitmaps here; this is only a + * cursory inspection. We will not return false negatives, but false positives are likely. + */ + private static boolean areIconsMaybeDifferent(Icon a, Icon b) { + if (a == b) { + return false; + } + if (a == null || b == null) { + return true; + } + if (a.sameAs(b)) { + return false; + } + final int aType = a.getType(); + if (aType != b.getType()) { + return true; + } + if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { + final Bitmap aBitmap = a.getBitmap(); + final Bitmap bBitmap = b.getBitmap(); + return aBitmap.getWidth() != bBitmap.getWidth() + || aBitmap.getHeight() != bBitmap.getHeight() + || aBitmap.getConfig() != bBitmap.getConfig() + || aBitmap.getGenerationId() != bBitmap.getGenerationId(); + } + return true; + } + + /** + * @hide + */ public static boolean areStyledNotificationsVisiblyDifferent(Builder first, Builder second) { if (first.getStyle() == null) { return second.getStyle() != null; @@ -7643,8 +7680,6 @@ public class Notification implements Parcelable /** * @hide - * Note that we aren't actually comparing the contents of the bitmaps here, so this - * is only doing a cursory inspection. Bitmaps of equal size will appear the same. */ @Override public boolean areNotificationsVisiblyDifferent(Style other) { @@ -7652,32 +7687,7 @@ public class Notification implements Parcelable return true; } BigPictureStyle otherS = (BigPictureStyle) other; - return areIconsObviouslyDifferent(getBigPicture(), otherS.getBigPicture()); - } - - private static boolean areIconsObviouslyDifferent(Icon a, Icon b) { - if (a == b) { - return false; - } - if (a == null || b == null) { - return true; - } - if (a.sameAs(b)) { - return false; - } - final int aType = a.getType(); - if (aType != b.getType()) { - return true; - } - if (aType == Icon.TYPE_BITMAP || aType == Icon.TYPE_ADAPTIVE_BITMAP) { - final Bitmap aBitmap = a.getBitmap(); - final Bitmap bBitmap = b.getBitmap(); - return aBitmap.getWidth() != bBitmap.getWidth() - || aBitmap.getHeight() != bBitmap.getHeight() - || aBitmap.getConfig() != bBitmap.getConfig() - || aBitmap.getGenerationId() != bBitmap.getGenerationId(); - } - return true; + return areIconsMaybeDifferent(getBigPicture(), otherS.getBigPicture()); } } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 303ada0fec9c..404f94ac77c4 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -188,6 +188,14 @@ public class TaskInfo { public int launchIntoPipHostTaskId; /** + * The task id of the parent Task of the launch-into-pip Activity, i.e., if task have more than + * one activity it will create new task for this activity, this id is the origin task id and + * the pip activity will be reparent to origin task when it exit pip mode. + * @hide + */ + public int lastParentTaskIdBeforePip; + + /** * The {@link Rect} copied from {@link DisplayCutout#getSafeInsets()} if the cutout is not of * (LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS), * {@code null} otherwise. @@ -512,6 +520,7 @@ public class TaskInfo { pictureInPictureParams = source.readTypedObject(PictureInPictureParams.CREATOR); shouldDockBigOverlays = source.readBoolean(); launchIntoPipHostTaskId = source.readInt(); + lastParentTaskIdBeforePip = source.readInt(); displayCutoutInsets = source.readTypedObject(Rect.CREATOR); topActivityInfo = source.readTypedObject(ActivityInfo.CREATOR); isResizeable = source.readBoolean(); @@ -558,6 +567,7 @@ public class TaskInfo { dest.writeTypedObject(pictureInPictureParams, flags); dest.writeBoolean(shouldDockBigOverlays); dest.writeInt(launchIntoPipHostTaskId); + dest.writeInt(lastParentTaskIdBeforePip); dest.writeTypedObject(displayCutoutInsets, flags); dest.writeTypedObject(topActivityInfo, flags); dest.writeBoolean(isResizeable); @@ -598,6 +608,7 @@ public class TaskInfo { + " pictureInPictureParams=" + pictureInPictureParams + " shouldDockBigOverlays=" + shouldDockBigOverlays + " launchIntoPipHostTaskId=" + launchIntoPipHostTaskId + + " lastParentTaskIdBeforePip=" + lastParentTaskIdBeforePip + " displayCutoutSafeInsets=" + displayCutoutInsets + " topActivityInfo=" + topActivityInfo + " launchCookies=" + launchCookies diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 90681cba7d83..00e6408a8dd3 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -603,6 +603,10 @@ public final class VirtualDeviceManager { mVirtualAudioDevice.close(); mVirtualAudioDevice = null; } + if (mVirtualCameraDevice != null) { + mVirtualCameraDevice.close(); + mVirtualCameraDevice = null; + } } /** diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index d3502c5254c8..d2dbd64ccd7a 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -309,14 +309,30 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall try { if (checkGetTypePermission(attributionSource, uri) == PermissionChecker.PERMISSION_GRANTED) { - final String type = mInterface.getType(uri); + String type; + if (checkPermission(Manifest.permission.GET_ANY_PROVIDER_TYPE, + attributionSource) == PermissionChecker.PERMISSION_GRANTED) { + /* + For calling packages having the special permission for any type, + the calling identity should be cleared before calling getType. + */ + final CallingIdentity origId = getContentProvider().clearCallingIdentity(); + try { + type = mInterface.getType(uri); + } finally { + getContentProvider().restoreCallingIdentity(origId); + } + } else { + type = mInterface.getType(uri); + } + if (type != null) { logGetTypeData(Binder.getCallingUid(), uri, type, true); } return type; } else { final int callingUid = Binder.getCallingUid(); - final long origId = Binder.clearCallingIdentity(); + final CallingIdentity origId = getContentProvider().clearCallingIdentity(); try { final String type = getTypeAnonymous(uri); if (type != null) { @@ -324,7 +340,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } return type; } finally { - Binder.restoreCallingIdentity(origId); + getContentProvider().restoreCallingIdentity(origId); } } } catch (RemoteException e) { diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java index 9a3b46dad2e7..da6656a0222f 100644 --- a/core/java/android/credentials/CredentialOption.java +++ b/core/java/android/credentials/CredentialOption.java @@ -16,16 +16,25 @@ package android.credentials; +import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; + import static java.util.Objects.requireNonNull; import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.content.ComponentName; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; + +import androidx.annotation.RequiresPermission; import com.android.internal.util.AnnotationValidations; import com.android.internal.util.Preconditions; +import java.util.Set; + /** * Information about a specific type of credential to be requested during a {@link * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor, @@ -66,6 +75,14 @@ public final class CredentialOption implements Parcelable { private final boolean mIsSystemProviderRequired; /** + * A list of {@link ComponentName}s corresponding to the providers that this option must be + * queried against. + */ + @NonNull + private final ArraySet<ComponentName> mAllowedProviders; + + + /** * Returns the requested credential type. */ @NonNull @@ -105,12 +122,22 @@ public final class CredentialOption implements Parcelable { return mIsSystemProviderRequired; } + /** + * Returns the set of {@link ComponentName} corresponding to providers that must receive + * this option. + */ + @NonNull + public Set<ComponentName> getAllowedProviders() { + return mAllowedProviders; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString8(mType); dest.writeBundle(mCredentialRetrievalData); dest.writeBundle(mCandidateQueryData); dest.writeBoolean(mIsSystemProviderRequired); + dest.writeArraySet(mAllowedProviders); } @Override @@ -125,6 +152,7 @@ public final class CredentialOption implements Parcelable { + ", requestData=" + mCredentialRetrievalData + ", candidateQueryData=" + mCandidateQueryData + ", isSystemProviderRequired=" + mIsSystemProviderRequired + + ", allowedProviders=" + mAllowedProviders + "}"; } @@ -139,17 +167,50 @@ public final class CredentialOption implements Parcelable { * provider * @throws IllegalArgumentException If type is empty. */ - public CredentialOption( + private CredentialOption( @NonNull String type, @NonNull Bundle credentialRetrievalData, @NonNull Bundle candidateQueryData, - boolean isSystemProviderRequired) { + boolean isSystemProviderRequired, + @NonNull ArraySet<ComponentName> allowedProviders) { mType = Preconditions.checkStringNotEmpty(type, "type must not be empty"); mCredentialRetrievalData = requireNonNull(credentialRetrievalData, "requestData must not be null"); mCandidateQueryData = requireNonNull(candidateQueryData, "candidateQueryData must not be null"); mIsSystemProviderRequired = isSystemProviderRequired; + mAllowedProviders = requireNonNull(allowedProviders, "providerFilterSer must" + + "not be empty"); + } + + /** + * Constructs a {@link CredentialOption}. + * + * @param type the requested credential type + * @param credentialRetrievalData the request data + * @param candidateQueryData the partial request data that will be sent to the provider + * during the initial credential candidate query stage + * @param isSystemProviderRequired whether the request must only be fulfilled by a system + * provider + * @throws IllegalArgumentException If type is empty, or null. + * @throws NullPointerException If {@code credentialRetrievalData}, or + * {@code candidateQueryData} is null. + * + * @deprecated replaced by Builder + */ + @Deprecated + public CredentialOption( + @NonNull String type, + @NonNull Bundle credentialRetrievalData, + @NonNull Bundle candidateQueryData, + boolean isSystemProviderRequired) { + this( + type, + credentialRetrievalData, + candidateQueryData, + isSystemProviderRequired, + new ArraySet<>() + ); } private CredentialOption(@NonNull Parcel in) { @@ -165,6 +226,8 @@ public final class CredentialOption implements Parcelable { mCandidateQueryData = candidateQueryData; AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData); mIsSystemProviderRequired = isSystemProviderRequired; + mAllowedProviders = (ArraySet<ComponentName>) in.readArraySet(null); + AnnotationValidations.validate(NonNull.class, null, mAllowedProviders); } @NonNull @@ -179,4 +242,108 @@ public final class CredentialOption implements Parcelable { return new CredentialOption(in); } }; + + /** A builder for {@link CredentialOption}. */ + public static final class Builder { + + @NonNull + private String mType; + + @NonNull + private Bundle mCredentialRetrievalData; + + @NonNull + private Bundle mCandidateQueryData; + + private boolean mIsSystemProviderRequired = false; + + @NonNull + private ArraySet<ComponentName> mAllowedProviders = new ArraySet<>(); + + /** + * @param type the type of the credential option + * @param credentialRetrievalData the full request data + * @param candidateQueryData the partial request data that will be sent to the provider + * during the initial credential candidate query stage. + * @throws IllegalArgumentException If {@code type} is null, or empty + * @throws NullPointerException If {@code credentialRetrievalData}, or + * {@code candidateQueryData} is null + */ + public Builder(@NonNull String type, @NonNull Bundle credentialRetrievalData, + @NonNull Bundle candidateQueryData) { + mType = Preconditions.checkStringNotEmpty(type, "type must not be " + + "null, or empty"); + mCredentialRetrievalData = requireNonNull(credentialRetrievalData, + "credentialRetrievalData must not be null"); + mCandidateQueryData = requireNonNull(candidateQueryData, + "candidateQueryData must not be null"); + } + + /** + * Sets a true/false value corresponding to whether this option must be serviced by + * system credentials providers only. + */ + @SuppressLint("MissingGetterMatchingBuilder") + @NonNull + public Builder setIsSystemProviderRequired(boolean isSystemProviderRequired) { + mIsSystemProviderRequired = isSystemProviderRequired; + return this; + } + + /** + * Adds a provider {@link ComponentName} to be queried while gathering credentials from + * credential providers on the device. + * + * If no candidate providers are specified, all user configured and system credential + * providers will be queried in the candidate query phase. + * + * If an invalid component name is provided, or a service corresponding to the + * component name does not exist on the device, that component name is ignored. + * If all component names are invalid, or not present on the device, no providers + * are queried and no credentials are retrieved. + * + * @throws NullPointerException If {@code allowedProvider} is null + */ + @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS) + @NonNull + public Builder addAllowedProvider(@NonNull ComponentName allowedProvider) { + mAllowedProviders.add(requireNonNull(allowedProvider, + "allowedProvider must not be null")); + return this; + } + + /** + * Sets a set of provider {@link ComponentName} to be queried while gathering credentials + * from credential providers on the device. + * + * If no candidate providers are specified, all user configured and system credential + * providers will be queried in the candidate query phase. + * + * If an invalid component name is provided, or a service corresponding to the + * component name does not exist on the device, that component name is ignored. + * If all component names are invalid, or not present on the device, no providers + * are queried and no credentials are retrieved. + * + * @throws NullPointerException If {@code allowedProviders} is null, or any of its + * elements are null. + */ + @RequiresPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS) + @NonNull + public Builder setAllowedProviders(@NonNull Set<ComponentName> allowedProviders) { + Preconditions.checkCollectionElementsNotNull( + allowedProviders, + /*valueName=*/ "allowedProviders"); + mAllowedProviders = new ArraySet<>(allowedProviders); + return this; + } + + /** + * Builds a {@link CredentialOption}. + */ + @NonNull + public CredentialOption build() { + return new CredentialOption(mType, mCredentialRetrievalData, mCandidateQueryData, + mIsSystemProviderRequired, mAllowedProviders); + } + } } diff --git a/core/java/android/credentials/CredentialProviderInfo.java b/core/java/android/credentials/CredentialProviderInfo.java index 7276770d281e..c224f01055e1 100644 --- a/core/java/android/credentials/CredentialProviderInfo.java +++ b/core/java/android/credentials/CredentialProviderInfo.java @@ -29,7 +29,9 @@ import android.text.TextUtils; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * {@link ServiceInfo} and meta-data about a credential provider. @@ -39,8 +41,9 @@ import java.util.List; @TestApi public final class CredentialProviderInfo implements Parcelable { @NonNull private final ServiceInfo mServiceInfo; - @NonNull private final List<String> mCapabilities = new ArrayList<>(); + @NonNull private final Set<String> mCapabilities = new HashSet<>(); @Nullable private final CharSequence mOverrideLabel; + @Nullable private CharSequence mSettingsSubtitle = null; private final boolean mIsSystemProvider; private final boolean mIsEnabled; @@ -53,6 +56,7 @@ public final class CredentialProviderInfo implements Parcelable { mServiceInfo = builder.mServiceInfo; mCapabilities.addAll(builder.mCapabilities); mIsSystemProvider = builder.mIsSystemProvider; + mSettingsSubtitle = builder.mSettingsSubtitle; mIsEnabled = builder.mIsEnabled; mOverrideLabel = builder.mOverrideLabel; } @@ -92,7 +96,11 @@ public final class CredentialProviderInfo implements Parcelable { /** Returns a list of capabilities this provider service can support. */ @NonNull public List<String> getCapabilities() { - return Collections.unmodifiableList(mCapabilities); + List<String> capabilities = new ArrayList<>(); + for (String capability : mCapabilities) { + capabilities.add(capability); + } + return Collections.unmodifiableList(capabilities); } /** Returns whether the provider is enabled by the user. */ @@ -100,6 +108,12 @@ public final class CredentialProviderInfo implements Parcelable { return mIsEnabled; } + /** Returns the settings subtitle. */ + @Nullable + public CharSequence getSettingsSubtitle() { + return mSettingsSubtitle; + } + /** Returns the component name for the service. */ @NonNull public ComponentName getComponentName() { @@ -110,9 +124,12 @@ public final class CredentialProviderInfo implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeTypedObject(mServiceInfo, flags); dest.writeBoolean(mIsSystemProvider); - dest.writeStringList(mCapabilities); dest.writeBoolean(mIsEnabled); TextUtils.writeToParcel(mOverrideLabel, dest, flags); + TextUtils.writeToParcel(mSettingsSubtitle, dest, flags); + + List<String> capabilities = getCapabilities(); + dest.writeStringList(capabilities); } @Override @@ -135,6 +152,9 @@ public final class CredentialProviderInfo implements Parcelable { + "overrideLabel=" + mOverrideLabel + ", " + + "settingsSubtitle=" + + mSettingsSubtitle + + ", " + "capabilities=" + String.join(",", mCapabilities) + "}"; @@ -143,9 +163,13 @@ public final class CredentialProviderInfo implements Parcelable { private CredentialProviderInfo(@NonNull Parcel in) { mServiceInfo = in.readTypedObject(ServiceInfo.CREATOR); mIsSystemProvider = in.readBoolean(); - in.readStringList(mCapabilities); mIsEnabled = in.readBoolean(); mOverrideLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mSettingsSubtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + + List<String> capabilities = new ArrayList<>(); + in.readStringList(capabilities); + mCapabilities.addAll(capabilities); } public static final @NonNull Parcelable.Creator<CredentialProviderInfo> CREATOR = @@ -165,8 +189,9 @@ public final class CredentialProviderInfo implements Parcelable { public static final class Builder { @NonNull private ServiceInfo mServiceInfo; - @NonNull private List<String> mCapabilities = new ArrayList<>(); + @NonNull private Set<String> mCapabilities = new HashSet<>(); private boolean mIsSystemProvider = false; + @Nullable private CharSequence mSettingsSubtitle = null; private boolean mIsEnabled = false; @Nullable private CharSequence mOverrideLabel = null; @@ -195,12 +220,28 @@ public final class CredentialProviderInfo implements Parcelable { return this; } + /** Sets the settings subtitle. */ + public @NonNull Builder setSettingsSubtitle(@Nullable CharSequence settingsSubtitle) { + mSettingsSubtitle = settingsSubtitle; + return this; + } + /** Sets a list of capabilities this provider service can support. */ public @NonNull Builder addCapabilities(@NonNull List<String> capabilities) { mCapabilities.addAll(capabilities); return this; } + /** + * Sets a list of capabilities this provider service can support. + * + * @hide + */ + public @NonNull Builder addCapabilities(@NonNull Set<String> capabilities) { + mCapabilities.addAll(capabilities); + return this; + } + /** Sets whether it is enabled by the user. */ public @NonNull Builder setEnabled(boolean isEnabled) { mIsEnabled = isEnabled; diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index e9df5533a907..1bc6099fd651 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -661,7 +661,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>Repeating burst requests are a simple way for an application to * maintain a preview or other continuous stream of frames where each - * request is different in a predicatable way, without having to continually + * request is different in a predictable way, without having to continually * submit requests through {@link #captureBurst}.</p> * * <p>To stop the repeating capture, call {@link #stopRepeating}. Any @@ -902,7 +902,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING OFFLINE_PROCESSING} * capability in {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}. When this method * is supported, applications can use it to improve the latency of closing camera or recreating - * capture session without losing the in progresss capture request outputs.</p> + * capture session without losing the in progress capture request outputs.</p> * * <p>Offline processing mode and the corresponding {@link CameraOfflineSession} differ from * a regular online camera capture session in several ways. Successful offline switches will @@ -1001,7 +1001,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * * <p>Note that for common usage scenarios like creating a new session or closing the camera * device, it is faster to call respective APIs directly (see below for more details) without - * calling into this method. This API is only useful when application wants to uncofigure the + * calling into this method. This API is only useful when application wants to unconfigure the * camera but keep the device open for later use.</p> * * <p>Creating a new capture session with {@link CameraDevice#createCaptureSession} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index dfb9cf65a4b9..0e4c3c0f12a1 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -651,7 +651,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * @param metadataClass The subclass of CameraMetadata that you want to get the keys for. * @param keyClass The class of the metadata key, e.g. CaptureRequest.Key.class * @param filterTags An array of tags to be used for filtering - * @param includeSynthetic Include public syntethic tag by default. + * @param includeSynthetic Include public synthetic tag by default. * * @return List of keys supported by this CameraDevice for metadataClass. * diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 42aa608c0ef8..5feda785ece3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -141,7 +141,7 @@ public abstract class CameraDevice implements AutoCloseable { * parameters. All automatic control is disabled (auto-exposure, auto-white * balance, auto-focus), and post-processing parameters are set to preview * quality. The manual capture parameters (exposure, sensitivity, and so on) - * are set to reasonable defaults, but should be overriden by the + * are set to reasonable defaults, but should be overridden by the * application depending on the intended use case. * This template is guaranteed to be supported on camera devices that support the * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR} @@ -680,7 +680,7 @@ public abstract class CameraDevice implements AutoCloseable { * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr> * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution GPU processing with preview.</td> </tr> * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution in-app processing with preview.</td> </tr> - * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processsing.</td> </tr> + * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td colspan="2" id="rb"></td> <td>Maximum-resolution two-input in-app processing.</td> </tr> * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td> <td>Video recording with maximum-size video snapshot</td> </tr> * <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Standard video recording plus maximum-resolution in-app processing.</td> </tr> * <tr> <td>{@code YUV }</td><td id="rb">{@code 640x480}</td> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Preview plus two-input maximum-resolution in-app processing.</td> </tr> @@ -722,7 +722,7 @@ public abstract class CameraDevice implements AutoCloseable { * <tr><th>Type</th><th id="rb">Max size</th><th>Type</th><th id="rb">Max size</th> </tr> * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV}</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution GPU processing with preview.</td> </tr> * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution in-app processing with preview.</td> </tr> - * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processsing.</td> </tr> + * <tr> <td>{@code YUV }</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV }</td><td id="rb">{@code MAXIMUM}</td> <td>Maximum-resolution two-input in-app processing.</td> </tr> * </table><br> * </p> * @@ -1137,7 +1137,7 @@ public abstract class CameraDevice implements AutoCloseable { * <tr><th colspan="13">Additional guaranteed combinations for ULTRA_HIGH_RESOLUTION sensors (YUV / PRIV inputs are guaranteed only if YUV / PRIVATE reprocessing are supported)</th></tr> * <tr> <th colspan="3" id="rb">Input</th> <th colspan="3" id="rb">Target 1</th> <th colspan="3" id="rb">Target 2</th> <th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr> * <tr> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th><th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th> <th>Type</th><th id="rb"> SC Map</th><th id="rb">Max size</th></tr> - * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with seperate preview</td> </tr> + * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td colspan="3" id="rb"></td> <td>RAW remosaic reprocessing with separate preview</td> </tr> * <tr> <td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td>{@code RAW}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code PRIV / YUV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG / YUV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>Ultra high res RAW -> JPEG / YUV with seperate preview</td> </tr> * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td><td id="rb">{@code YUV / PRIV}</td><td id="rb">{@code DEFAULT}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code JPEG }</td><td id="rb">{@code MAX_RES}</td><td id="rb">{@code MAX}</td> <td> Ultra high res PRIV / YUV -> YUV / JPEG reprocessing with seperate preview</td> </tr> * </table><br> @@ -1260,7 +1260,7 @@ public abstract class CameraDevice implements AutoCloseable { * settings by calling {@link CaptureRequest.Builder#setPhysicalCameraKey}.</p> * * <p>Individual physical camera settings will only be honored for camera session - * that was initialiazed with corresponding physical camera id output configuration + * that was initialized with corresponding physical camera id output configuration * {@link OutputConfiguration#setPhysicalCameraId} and the same output targets are * also attached in the request by {@link CaptureRequest.Builder#addTarget}.</p> * diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 696873f7e5e4..144b1de148b4 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -256,7 +256,7 @@ public final class CameraManager { /** * Similar to getCameraIdList(). However, getCamerIdListNoLazy() necessarily communicates with - * cameraserver in order to get the list of camera ids. This is to faciliate testing since some + * cameraserver in order to get the list of camera ids. This is to facilitate testing since some * camera ids may go 'offline' without callbacks from cameraserver because of changes in * SYSTEM_CAMERA permissions (though this is not a changeable permission, tests may call * adopt(drop)ShellPermissionIdentity() and effectively change their permissions). This call @@ -560,7 +560,7 @@ public final class CameraManager { } // Query the characteristics of all physical sub-cameras, and combine the multi-resolution - // stream configurations. Alternatively, for ultra-high resolution camera, direclty use + // stream configurations. Alternatively, for ultra-high resolution camera, directly use // its multi-resolution stream configurations. Note that framework derived formats such as // HEIC and DEPTH_JPEG aren't supported as multi-resolution input or output formats. Set<String> physicalCameraIds = info.getPhysicalCameraIds(); @@ -835,7 +835,7 @@ public final class CameraManager { * Opening the same camera ID twice in the same application will similarly cause the * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback * being fired for the {@link CameraDevice} from the first open call and all ongoing tasks - * being droppped.</p> + * being dropped.</p> * * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up @@ -1759,7 +1759,7 @@ public final class CameraManager { private final Set<Set<String>> mConcurrentCameraIdCombinations = new ArraySet<Set<String>>(); - // Registered availablility callbacks and their executors + // Registered availability callbacks and their executors private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<AvailabilityCallback, Executor>(); @@ -2846,7 +2846,7 @@ public final class CameraManager { // Tell listeners that the cameras and torch modes are unavailable and schedule a // reconnection to camera service. When camera service is reconnected, the camera // and torch statuses will be updated. - // Iterate from the end to the beginning befcause onStatusChangedLocked removes + // Iterate from the end to the beginning because onStatusChangedLocked removes // entries from the ArrayMap. for (int i = mDeviceStatus.size() - 1; i >= 0; i--) { String cameraId = mDeviceStatus.keyAt(i); diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index ea998473155f..a7e28e2f40d1 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -159,7 +159,7 @@ public abstract class CameraMetadata<TKey> { * Optionally, if {@code filterTags} is not {@code null}, then filter out any keys * whose native {@code tag} is not in {@code filterTags}. The {@code filterTags} array will be * sorted as a side effect. - * {@code includeSynthetic} Includes public syntenthic fields by default. + * {@code includeSynthetic} Includes public synthetic fields by default. * </p> */ /*package*/ @SuppressWarnings("unchecked") @@ -2308,7 +2308,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>An external flash has been turned on.</p> * <p>It informs the camera device that an external flash has been turned on, and that - * metering (and continuous focus if active) should be quickly recaculated to account + * metering (and continuous focus if active) should be quickly recalculated to account * for the external flash. Otherwise, this mode acts like ON.</p> * <p>When the external flash is turned off, AE mode should be changed to one of the * other available AE modes.</p> diff --git a/core/java/android/hardware/camera2/CameraOfflineSession.java b/core/java/android/hardware/camera2/CameraOfflineSession.java index 312559c6c7df..c2198867940f 100644 --- a/core/java/android/hardware/camera2/CameraOfflineSession.java +++ b/core/java/android/hardware/camera2/CameraOfflineSession.java @@ -152,7 +152,7 @@ public abstract class CameraOfflineSession extends CameraCaptureSession { * * <p>Closing a session is idempotent; closing more than once has no effect.</p> * - * @throws IllegalStateException if the offline sesion is not ready. + * @throws IllegalStateException if the offline session is not ready. */ @Override public abstract void close(); diff --git a/core/java/android/hardware/camera2/DngCreator.java b/core/java/android/hardware/camera2/DngCreator.java index cc484eaf265c..1ae2fe182427 100644 --- a/core/java/android/hardware/camera2/DngCreator.java +++ b/core/java/android/hardware/camera2/DngCreator.java @@ -482,7 +482,7 @@ public final class DngCreator implements AutoCloseable { } private static final int DEFAULT_PIXEL_STRIDE = 2; // bytes per sample - private static final int BYTES_PER_RGB_PIX = 3; // byts per pixel + private static final int BYTES_PER_RGB_PIX = 3; // bytes per pixel // TIFF tag values needed to map between public API and TIFF spec private static final int TAG_ORIENTATION_UNKNOWN = 9; diff --git a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl index 34d016adbc06..7c54a9b01dde 100644 --- a/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl +++ b/core/java/android/hardware/camera2/extension/CameraOutputConfig.aidl @@ -36,4 +36,5 @@ parcelable CameraOutputConfig int surfaceGroupId; String physicalCameraId; List<CameraOutputConfig> sharedSurfaceConfigs; + boolean isMultiResolutionOutput; } diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 2fa8b877a2df..cfade5532df7 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -258,6 +258,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes OutputConfiguration cameraOutput = new OutputConfiguration(output.surfaceGroupId, outputSurface); + if (output.isMultiResolutionOutput) { + cameraOutput.setMultiResolutionOutput(); + } if ((output.sharedSurfaceConfigs != null) && !output.sharedSurfaceConfigs.isEmpty()) { cameraOutput.enableSurfaceSharing(); for (CameraOutputConfig sharedOutputConfig : output.sharedSurfaceConfigs) { diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index 86c453b56c5b..0a4a1f0176c7 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -607,7 +607,7 @@ public final class MandatoryStreamCombination { new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM), new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM) }, - "Maximum-resolution two-input in-app processsing"), + "Maximum-resolution two-input in-app processing"), new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW), @@ -891,7 +891,7 @@ public final class MandatoryStreamCombination { new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.s720p), new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)}, - "Standard stil image capture"), + "Standard still image capture"), new StreamCombinationTemplate(new StreamTemplate [] { new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.s720p), new StreamTemplate(ImageFormat.JPEG, SizeThreshold.s1440p)}, diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java index 857f62dfdd99..21540bf1a18b 100644 --- a/core/java/android/hardware/camera2/params/OutputConfiguration.java +++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java @@ -165,7 +165,7 @@ public final class OutputConfiguration implements Parcelable { * device runs in fixed frame rate. The timestamp is roughly in the same time base as * {@link android.os.SystemClock#uptimeMillis}.</li> * <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link - * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is + * android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usage flag, the timestamp base is * {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as * {@link android.os.SystemClock#uptimeMillis}.</li> * <li> For all other cases, the timestamp base is {@link #TIMESTAMP_BASE_SENSOR}, the same @@ -418,7 +418,7 @@ public final class OutputConfiguration implements Parcelable { * call, or no non-negative group ID has been set. * @hide */ - void setMultiResolutionOutput() { + public void setMultiResolutionOutput() { if (mIsShared) { throw new IllegalStateException("Multi-resolution output flag must not be set for " + "configuration with surface sharing"); @@ -654,7 +654,7 @@ public final class OutputConfiguration implements Parcelable { mSurfaceType = SURFACE_TYPE_SURFACE_TEXTURE; } else { mSurfaceType = SURFACE_TYPE_UNKNOWN; - throw new IllegalArgumentException("Unknow surface source class type"); + throw new IllegalArgumentException("Unknown surface source class type"); } if (surfaceSize.getWidth() == 0 || surfaceSize.getHeight() == 0) { @@ -715,7 +715,7 @@ public final class OutputConfiguration implements Parcelable { * The supported surfaces for sharing must be of type SurfaceTexture, SurfaceView, * MediaRecorder, MediaCodec, or implementation defined ImageReader.</p> * - * <p>This function must not be called from OuptutConfigurations created by {@link + * <p>This function must not be called from OutputConfigurations created by {@link * #createInstancesForMultiResolutionOutput}.</p> * * @throws IllegalStateException If this OutputConfiguration is created via {@link @@ -934,7 +934,7 @@ public final class OutputConfiguration implements Parcelable { * * <p> Surfaces added via calls to {@link #addSurface} can also be removed from the * OutputConfiguration. The only notable exception is the surface associated with - * the OutputConfigration see {@link #getSurface} which was passed as part of the constructor + * the OutputConfiguration see {@link #getSurface} which was passed as part of the constructor * or was added first in the deferred case * {@link OutputConfiguration#OutputConfiguration(Size, Class)}.</p> * @@ -962,7 +962,7 @@ public final class OutputConfiguration implements Parcelable { * for scenarios where the immediate consumer target isn't sufficient to indicate the stream's * usage.</p> * - * <p>The main difference beteween stream use case and capture intent is that the former + * <p>The main difference between stream use case and capture intent is that the former * enables the camera device to optimize camera hardware and software pipelines based on user * scenarios for each stream, whereas the latter is mainly a hint to camera to decide * optimal 3A strategy that's applicable to the whole session. The camera device carries out @@ -1123,7 +1123,7 @@ public final class OutputConfiguration implements Parcelable { * CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is * {@link CameraMetadata#SENSOR_READOUT_TIMESTAMP_HARDWARE}.</p> * - * <p>As long as readout timestamp is supported, if the timestamp base isi + * <p>As long as readout timestamp is supported, if the timestamp base is * {@link #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}, or if the timestamp base is DEFAULT for a * SurfaceView output, the image timestamps for the output are always readout time regardless * of whether this function is called.</p> @@ -1420,9 +1420,9 @@ public final class OutputConfiguration implements Parcelable { */ @Override public int hashCode() { - // Need ensure that the hashcode remains unchanged after adding a deferred surface. Otherwise - // the deferred output configuration will be lost in the camera streammap after the deferred - // surface is set. + // Need ensure that the hashcode remains unchanged after adding a deferred surface. + // Otherwise the deferred output configuration will be lost in the camera stream map + // after the deferred surface is set. if (mIsDeferredConfig) { return HashCodeHelpers.hashCode( mRotation, mConfiguredSize.hashCode(), mConfiguredFormat, mConfiguredDataspace, @@ -1446,7 +1446,7 @@ public final class OutputConfiguration implements Parcelable { private static final String TAG = "OutputConfiguration"; // A surfaceGroupId counter used for MultiResolutionImageReader. Its value is - // incremented everytime {@link createInstancesForMultiResolutionOutput} is called. + // incremented every time {@link createInstancesForMultiResolutionOutput} is called. private static int MULTI_RESOLUTION_GROUP_ID_COUNTER = 0; private ArrayList<Surface> mSurfaces; diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java index 5a48176d29e7..aabe149d16f5 100644 --- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java +++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java @@ -1790,7 +1790,7 @@ public final class StreamConfigurationMap { * * <p>{@code ValidOutputFormatsForInput([in:%s(%d), out:%s(%d), ... %s(%d)], * ... [in:%s(%d), out:%s(%d), ... %s(%d)])}, where {@code [in:%s(%d), out:%s(%d), ... %s(%d)]} - * represents an input fomat and its valid output formats.</p> + * represents an input format and its valid output formats.</p> * * <p>{@code HighSpeedVideoConfigurations([w:%d, h:%d, min_fps:%d, max_fps:%d], * ... [w:%d, h:%d, min_fps:%d, max_fps:%d])}, where diff --git a/core/java/android/hardware/display/HdrConversionMode.java b/core/java/android/hardware/display/HdrConversionMode.java index 49e5eff95a25..5fccb5e6d9db 100644 --- a/core/java/android/hardware/display/HdrConversionMode.java +++ b/core/java/android/hardware/display/HdrConversionMode.java @@ -29,9 +29,6 @@ import java.lang.annotation.RetentionPolicy; /** * Describes the HDR conversion mode for a device. * - * This class is used when user changes the HDR conversion mode of the device via - * {@link DisplayManager#setHdrConversionMode(HdrConversionMode)}. - * <p> * HDR conversion mode has a conversionMode and preferredHdrOutputType. </p><p> * The conversionMode can be one of: * {@link HdrConversionMode#HDR_CONVERSION_UNSUPPORTED} : HDR conversion is unsupported. In this diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java index 490e55ba260f..3fffb3ca2c7b 100644 --- a/core/java/android/hardware/display/VirtualDisplayConfig.java +++ b/core/java/android/hardware/display/VirtualDisplayConfig.java @@ -438,7 +438,7 @@ public final class VirtualDisplayConfig implements Parcelable { * * <p>For best results, specify a divisor of the physical refresh rate, e.g., 30 or 60 on * a 120hz display. If an arbitrary refresh rate is specified, the rate will be rounded up - * down to a divisor of the physical display. If unset or zero, the virtual display will be + * to a divisor of the physical display. If unset or zero, the virtual display will be * refreshed at the physical display refresh rate. * * @see Display#getRefreshRate() diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java index 089b1599ec06..0ba0c5a8d13b 100644 --- a/core/java/android/nfc/tech/IsoDep.java +++ b/core/java/android/nfc/tech/IsoDep.java @@ -88,6 +88,7 @@ public final class IsoDep extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param timeout timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void setTimeout(int timeout) { try { @@ -106,6 +107,7 @@ public final class IsoDep extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @return timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public int getTimeout() { try { @@ -167,6 +169,7 @@ public final class IsoDep extends BasicTagTechnology { * @return response bytes received, will not be null * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or this operation is canceled + * @throws SecurityException if the tag object is reused after the tag has left the field */ public byte[] transceive(byte[] data) throws IOException { return transceive(data, true); @@ -193,6 +196,7 @@ public final class IsoDep extends BasicTagTechnology { * support. * * @return whether the NFC adapter on this device supports extended length APDUs. + * @throws SecurityException if the tag object is reused after the tag has left the field */ public boolean isExtendedLengthApduSupported() { try { diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java index 080e058737b6..26f54e692289 100644 --- a/core/java/android/nfc/tech/MifareClassic.java +++ b/core/java/android/nfc/tech/MifareClassic.java @@ -597,6 +597,7 @@ public final class MifareClassic extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param timeout timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void setTimeout(int timeout) { try { @@ -615,6 +616,7 @@ public final class MifareClassic extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @return timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public int getTimeout() { try { diff --git a/core/java/android/nfc/tech/MifareUltralight.java b/core/java/android/nfc/tech/MifareUltralight.java index dec2c6548ab5..c0416a39ba76 100644 --- a/core/java/android/nfc/tech/MifareUltralight.java +++ b/core/java/android/nfc/tech/MifareUltralight.java @@ -236,6 +236,7 @@ public final class MifareUltralight extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param timeout timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void setTimeout(int timeout) { try { @@ -255,6 +256,7 @@ public final class MifareUltralight extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @return timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public int getTimeout() { try { diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java index 39c355ad918a..7d83f157a314 100644 --- a/core/java/android/nfc/tech/Ndef.java +++ b/core/java/android/nfc/tech/Ndef.java @@ -261,6 +261,7 @@ public final class Ndef extends BasicTagTechnology { * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or the operation is canceled * @throws FormatException if the NDEF Message on the tag is malformed + * @throws SecurityException if the tag object is reused after the tag has left the field */ public NdefMessage getNdefMessage() throws IOException, FormatException { checkConnected(); @@ -301,6 +302,7 @@ public final class Ndef extends BasicTagTechnology { * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or the operation is canceled * @throws FormatException if the NDEF Message to write is malformed + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void writeNdefMessage(NdefMessage msg) throws IOException, FormatException { checkConnected(); @@ -339,6 +341,7 @@ public final class Ndef extends BasicTagTechnology { * <p>Does not cause any RF activity and does not block. * * @return true if it is possible to make this tag read-only + * @throws SecurityException if the tag object is reused after the tag has left the field */ public boolean canMakeReadOnly() { INfcTag tagService = mTag.getTagService(); @@ -370,6 +373,7 @@ public final class Ndef extends BasicTagTechnology { * @return true on success, false if it is not possible to make this tag read-only * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or the operation is canceled + * @throws SecurityException if the tag object is reused after the tag has left the field */ public boolean makeReadOnly() throws IOException { checkConnected(); diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java index 4175cd08804c..f19d30258393 100644 --- a/core/java/android/nfc/tech/NdefFormatable.java +++ b/core/java/android/nfc/tech/NdefFormatable.java @@ -111,6 +111,7 @@ public final class NdefFormatable extends BasicTagTechnology { * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or the operation is canceled * @throws FormatException if the NDEF Message to write is malformed + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void formatReadOnly(NdefMessage firstMessage) throws IOException, FormatException { format(firstMessage, true); diff --git a/core/java/android/nfc/tech/NfcA.java b/core/java/android/nfc/tech/NfcA.java index 88730f9af3df..7e6648361670 100644 --- a/core/java/android/nfc/tech/NfcA.java +++ b/core/java/android/nfc/tech/NfcA.java @@ -141,6 +141,7 @@ public final class NfcA extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param timeout timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void setTimeout(int timeout) { try { @@ -159,6 +160,7 @@ public final class NfcA extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @return timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public int getTimeout() { try { diff --git a/core/java/android/nfc/tech/NfcF.java b/core/java/android/nfc/tech/NfcF.java index 44871212daef..2ccd38875f07 100644 --- a/core/java/android/nfc/tech/NfcF.java +++ b/core/java/android/nfc/tech/NfcF.java @@ -145,6 +145,7 @@ public final class NfcF extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @param timeout timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void setTimeout(int timeout) { try { @@ -163,6 +164,7 @@ public final class NfcF extends BasicTagTechnology { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @return timeout value in milliseconds + * @throws SecurityException if the tag object is reused after the tag has left the field */ public int getTimeout() { try { diff --git a/core/java/android/nfc/tech/TagTechnology.java b/core/java/android/nfc/tech/TagTechnology.java index 0e2c7c18c61c..839fe429b338 100644 --- a/core/java/android/nfc/tech/TagTechnology.java +++ b/core/java/android/nfc/tech/TagTechnology.java @@ -176,6 +176,7 @@ public interface TagTechnology extends Closeable { * @see #close() * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or connect is canceled + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void connect() throws IOException; @@ -193,6 +194,7 @@ public interface TagTechnology extends Closeable { * @see #close() * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or connect is canceled + * @throws SecurityException if the tag object is reused after the tag has left the field * @hide */ public void reconnect() throws IOException; @@ -205,6 +207,7 @@ public interface TagTechnology extends Closeable { * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. * * @see #connect() + * @throws SecurityException if the tag object is reused after the tag has left the field */ public void close() throws IOException; diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index b478a37984e0..af09a0662795 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -1295,19 +1295,31 @@ public final class FileUtils { * value, such as 256MB or 32GB. This avoids showing weird values like * "29.5GB" in UI. * + * Some storage devices are still using GiB (powers of 1024) over + * GB (powers of 1000) measurements and this method takes it into account. + * + * Round ranges: + * ... + * [256 GiB + 1; 512 GiB] -> 512 GB + * [512 GiB + 1; 1 TiB] -> 1 TB + * [1 TiB + 1; 2 TiB] -> 2 TB + * etc + * * @hide */ public static long roundStorageSize(long size) { long val = 1; - long pow = 1; - while ((val * pow) < size) { + long kiloPow = 1; + long kibiPow = 1; + while ((val * kibiPow) < size) { val <<= 1; if (val > 512) { val = 1; - pow *= 1000; + kibiPow *= 1024; + kiloPow *= 1000; } } - return val * pow; + return val * kiloPow; } private static long toBytes(long value, String unit) { diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 1673ade04150..e784c2669575 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -4499,17 +4499,28 @@ public final class Parcel { public void writeToParcel(Parcel out) { Parcel source = mSource; if (source != null) { - out.appendFrom(source, mPosition, mLength); - } else { - out.writeValue(mObject); + synchronized (source) { + if (mSource != null) { + out.appendFrom(source, mPosition, mLength); + return; + } + } } + + out.writeValue(mObject); } public boolean hasFileDescriptors() { Parcel source = mSource; - return (source != null) - ? source.hasFileDescriptors(mPosition, mLength) - : Parcel.hasFileDescriptors(mObject); + if (source != null) { + synchronized (source) { + if (mSource != null) { + return source.hasFileDescriptors(mPosition, mLength); + } + } + } + + return Parcel.hasFileDescriptors(mObject); } @Override diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 123f48067be8..89b768d7dff7 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14777,23 +14777,6 @@ public final class Settings { "adaptive_battery_management_enabled"; /** - * Whether or not apps are allowed into the - * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} bucket. - * Type: int (0 for false, 1 for true) - * Default: {@value #DEFAULT_ENABLE_RESTRICTED_BUCKET} - * - * @hide - */ - @Readable - public static final String ENABLE_RESTRICTED_BUCKET = "enable_restricted_bucket"; - - /** - * @see #ENABLE_RESTRICTED_BUCKET - * @hide - */ - public static final int DEFAULT_ENABLE_RESTRICTED_BUCKET = 1; - - /** * Whether or not app auto restriction is enabled. When it is enabled, settings app will * auto restrict the app if it has bad behavior (e.g. hold wakelock for long time). * diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index 3190c69abd92..ea86fcc519b9 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -33,17 +33,28 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; +import android.text.TextUtils; +import android.util.AttributeSet; import android.util.Log; import android.util.Slog; +import android.util.Xml; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -58,6 +69,11 @@ import java.util.Set; public final class CredentialProviderInfoFactory { private static final String TAG = "CredentialProviderInfoFactory"; + private static final String TAG_CREDENTIAL_PROVIDER = "credential-provider"; + private static final String TAG_CAPABILITIES = "capabilities"; + private static final String TAG_CAPABILITY = "capability"; + private static final String ATTR_NAME = "name"; + /** * Constructs an information instance of the credential provider. * @@ -118,8 +134,8 @@ public final class CredentialProviderInfoFactory { } /** - * Constructs an information instance of the credential provider for testing purposes. Does - * not run any verifications and passes parameters as is. + * Constructs an information instance of the credential provider for testing purposes. Does not + * run any verifications and passes parameters as is. */ @VisibleForTesting public static CredentialProviderInfo createForTests( @@ -134,7 +150,6 @@ public final class CredentialProviderInfoFactory { .setSystemProvider(isSystemProvider) .addCapabilities(capabilities) .build(); - } private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException { @@ -194,10 +209,8 @@ public final class CredentialProviderInfoFactory { private static CredentialProviderInfo.Builder populateMetadata( @NonNull Context context, ServiceInfo serviceInfo) { requireNonNull(context, "context must not be null"); - - final CredentialProviderInfo.Builder builder = - new CredentialProviderInfo.Builder(serviceInfo); final PackageManager pm = context.getPackageManager(); + CredentialProviderInfo.Builder builder = new CredentialProviderInfo.Builder(serviceInfo); // 1. Get the metadata for the service. final Bundle metadata = serviceInfo.metaData; @@ -206,46 +219,165 @@ public final class CredentialProviderInfoFactory { return builder; } - // 2. Extract the capabilities from the bundle. + // 2. Get the resources for the application. + Resources resources = null; + try { + resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to get app resources", e); + } + + // 3. Stop if we are missing data. + if (metadata == null || resources == null) { + Log.i(TAG, "populateMetadata - resources is null"); + return builder; + } + + // 4. Extract the XML metadata. + try { + builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources); + } catch (Exception e) { + Log.e(TAG, "Failed to get XML metadata", e); + } + + // 5. Extract the legacy metadata. + try { + builder.addCapabilities( + populateLegacyProviderCapabilities(resources, metadata, serviceInfo)); + } catch (Exception e) { + Log.e(TAG, "Failed to get legacy metadata ", e); + } + + return builder; + } + + private static CredentialProviderInfo.Builder extractXmlMetadata( + @NonNull Context context, + @NonNull CredentialProviderInfo.Builder builder, + @NonNull ServiceInfo serviceInfo, + @NonNull PackageManager pm, + @NonNull Resources resources) { + final XmlResourceParser parser = + serviceInfo.loadXmlMetaData(pm, CredentialProviderService.SERVICE_META_DATA); + if (parser == null) { + return builder; + } + try { - Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); - if (metadata == null || resources == null) { - Log.i(TAG, "populateMetadata - resources is null"); - return builder; + int type = 0; + while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { + type = parser.next(); } - builder.addCapabilities(populateProviderCapabilities(resources, metadata, serviceInfo)); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, e.getMessage()); + // This is matching a <credential-provider /> tag in the XML. + if (TAG_CREDENTIAL_PROVIDER.equals(parser.getName())) { + final AttributeSet allAttributes = Xml.asAttributeSet(parser); + TypedArray afsAttributes = null; + try { + afsAttributes = + resources.obtainAttributes( + allAttributes, + com.android.internal.R.styleable.CredentialProvider); + builder.setSettingsSubtitle( + afsAttributes.getString( + R.styleable.CredentialProvider_settingsSubtitle)); + } catch (Exception e) { + Log.e(TAG, "Failed to get XML attr", e); + } finally { + if (afsAttributes != null) { + afsAttributes.recycle(); + } + } + builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); + } else { + Log.e(TAG, "Meta-data does not start with credential-provider-service tag"); + } + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Error parsing credential provider service meta-data", e); } return builder; } - private static List<String> populateProviderCapabilities( + private static Set<String> parseXmlProviderOuterCapabilities( + XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { + final Set<String> capabilities = new HashSet<>(); + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + if (TAG_CAPABILITIES.equals(parser.getName())) { + capabilities.addAll(parseXmlProviderInnerCapabilities(parser, resources)); + } + } + + return capabilities; + } + + private static List<String> parseXmlProviderInnerCapabilities( + XmlPullParser parser, Resources resources) throws IOException, XmlPullParserException { + List<String> capabilities = new ArrayList<>(); + + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + if (TAG_CAPABILITY.equals(parser.getName())) { + String name = parser.getAttributeValue(null, ATTR_NAME); + if (name != null && !TextUtils.isEmpty(name)) { + capabilities.add(name); + } + } + } + + return capabilities; + } + + private static Set<String> populateLegacyProviderCapabilities( Resources resources, Bundle metadata, ServiceInfo serviceInfo) { - List<String> output = new ArrayList<>(); - String[] capabilities = new String[0]; + Set<String> output = new HashSet<>(); + Set<String> capabilities = new HashSet<>(); try { - capabilities = + String[] discovered = resources.getStringArray( metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY)); - } catch (Resources.NotFoundException e) { - Slog.e(TAG, "Failed to get capabilities: " + e.getMessage()); + if (discovered != null) { + capabilities.addAll(Arrays.asList(discovered)); + } + } catch (Resources.NotFoundException | NullPointerException e) { + Log.e(TAG, "Failed to get capabilities: ", e); + } + + try { + String[] discovered = + metadata.getStringArray(CredentialProviderService.CAPABILITY_META_DATA_KEY); + if (discovered != null) { + capabilities.addAll(Arrays.asList(discovered)); + } + } catch (Resources.NotFoundException | NullPointerException e) { + Log.e(TAG, "Failed to get capabilities: ", e); } - if (capabilities == null || capabilities.length == 0) { - Slog.e(TAG, "No capabilities found for provider:" + serviceInfo.packageName); + if (capabilities.size() == 0) { + Log.e(TAG, "No capabilities found for provider:" + serviceInfo); return output; } for (String capability : capabilities) { - if (capability.isEmpty()) { - Slog.e(TAG, "Skipping empty capability"); + if (capability == null || capability.isEmpty()) { + Log.w(TAG, "Skipping empty/null capability"); continue; } - Slog.e(TAG, "Capabilities found for provider: " + capability); + Log.i(TAG, "Capabilities found for provider: " + capability); output.add(capability); } return output; @@ -361,7 +493,8 @@ public final class CredentialProviderInfoFactory { try { DevicePolicyManager dpm = newContext.getSystemService(DevicePolicyManager.class); - return dpm.getCredentialManagerPolicy(); + PackagePolicy pp = dpm.getCredentialManagerPolicy(); + return pp; } catch (SecurityException e) { // If the current user is not enrolled in DPM then this can throw a security error. Log.e(TAG, "Failed to get device policy: " + e); diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java index e88474d86798..6824159706cd 100644 --- a/core/java/android/service/credentials/CredentialProviderService.java +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -156,8 +156,43 @@ public abstract class CredentialProviderService extends Service { private static final String TAG = "CredProviderService"; + /** + * The list of capabilities exposed by a credential provider. + * + * @deprecated Replaced with {@link android.service.credentials#SERVICE_META_DATA} + */ + @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities"; + /** + * Name under which a Credential Provider service component publishes information + * about itself. This meta-data must reference an XML resource containing + * an + * <code><{@link android.R.styleable#CredentialProvider credential-provider}></code> + * tag. + * + * For example (AndroidManifest.xml): + * <code> + * <meta-data + * android:name="android.credentials.provider" + * android:resource="@xml/provider"/> + * </code> + * + * For example (xml/provider.xml): + * <code> + * <credential-provider xmlns:android="http://schemas.android.com/apk/res/android" + * android:settingsSubtitle="@string/providerSubtitle"> + * <capabilities> + * <capability>@string/passwords</capability> + * <capability>@string/passkeys</capability> + * </capabilities> + * <string name="passwords">android.credentials.TYPE_PASSWORD_CREDENTIAL</string> + * <string name="passkeys">android.credentials.TYPE_PUBLIC_KEY_CREDENTIAL</string> + * </credential-provider> + * </code> + */ + public static final String SERVICE_META_DATA = "android.credentials.provider"; + /** @hide */ public static final String TEST_SYSTEM_PROVIDER_META_DATA_KEY = "android.credentials.testsystemprovider"; diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 8d95c0251203..0b947fc18237 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -60,6 +60,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -97,6 +98,7 @@ import android.view.WindowManagerGlobal; import android.window.ClientWindowFrames; import android.window.ScreenCapture; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; @@ -104,9 +106,10 @@ import com.android.internal.view.BaseSurfaceHolder; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; @@ -167,11 +170,12 @@ public abstract class WallpaperService extends Service { private static final int MSG_REPORT_SHOWN = 10150; private static final int MSG_UPDATE_DIMMING = 10200; private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210; - private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY, - Float.NEGATIVE_INFINITY); + /** limit calls to {@link Engine#onComputeColors} to at most once per second */ private static final int NOTIFY_COLORS_RATE_LIMIT_MS = 1000; - private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 1000; + + /** limit calls to {@link Engine#processLocalColorsInternal} to at most once per 2 seconds */ + private static final int PROCESS_LOCAL_COLORS_INTERVAL_MS = 2000; private static final boolean ENABLE_WALLPAPER_DIMMING = SystemProperties.getBoolean("persist.debug.enable_wallpaper_dimming", true); @@ -180,6 +184,9 @@ public abstract class WallpaperService extends Service { private final ArrayMap<IBinder, IWallpaperEngineWrapper> mActiveEngines = new ArrayMap<>(); + private Handler mBackgroundHandler; + private HandlerThread mBackgroundThread; + static final class WallpaperCommand { String action; int x; @@ -198,14 +205,6 @@ public abstract class WallpaperService extends Service { */ public class Engine { IWallpaperEngineWrapper mIWallpaperEngine; - final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); - final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); - - // 2D matrix [x][y] to represent a page of a portion of a window - EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; - Bitmap mLastScreenshot; - int mLastWindowPage = -1; - private boolean mResetWindowPages; // Copies from mIWallpaperEngine. HandlerCaller mCaller; @@ -266,11 +265,34 @@ public abstract class WallpaperService extends Service { final Object mLock = new Object(); boolean mOffsetMessageEnqueued; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - float mPendingXOffset; - float mPendingYOffset; - float mPendingXOffsetStep; - float mPendingYOffsetStep; + @GuardedBy("mLock") + private float mPendingXOffset; + @GuardedBy("mLock") + private float mPendingYOffset; + @GuardedBy("mLock") + private float mPendingXOffsetStep; + @GuardedBy("mLock") + private float mPendingYOffsetStep; + + /** + * local color extraction related fields. When a user calls `addLocalColorAreas` + */ + @GuardedBy("mLock") + private final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); + + @GuardedBy("mLock") + private final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); + private long mLastProcessLocalColorsTimestamp; + private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); + private int mPixelCopyCount = 0; + // 2D matrix [x][y] to represent a page of a portion of a window + @GuardedBy("mLock") + private EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; + private Bitmap mLastScreenshot; + private boolean mResetWindowPages; + boolean mPendingSync; MotionEvent mPendingMove; boolean mIsInAmbientMode; @@ -279,12 +301,8 @@ public abstract class WallpaperService extends Service { private long mLastColorInvalidation; private final Runnable mNotifyColorsChanged = this::notifyColorsChanged; - // used to throttle processLocalColors - private long mLastProcessLocalColorsTimestamp; - private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); private final Supplier<Long> mClockFunction; private final Handler mHandler; - private Display mDisplay; private Context mDisplayContext; private int mDisplayState; @@ -854,7 +872,7 @@ public abstract class WallpaperService extends Service { + "was not established."); } mResetWindowPages = true; - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); } @@ -1389,10 +1407,9 @@ public abstract class WallpaperService extends Service { mIsCreating = false; mSurfaceCreated = true; if (redrawNeeded) { - resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE); - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } reposition(); reportEngineShown(shouldWaitForEngineShown()); @@ -1536,7 +1553,7 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(false); - if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); + if (mReportedVisible) processLocalColors(); } else { AnimationHandler.requestAnimatorsEnabled(visible, this); } @@ -1640,14 +1657,14 @@ public abstract class WallpaperService extends Service { } // setup local color extraction data - processLocalColors(xOffset, xOffsetStep); + processLocalColors(); } /** * Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of * {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls. */ - private void processLocalColors(float xOffset, float xOffsetStep) { + private void processLocalColors() { if (mProcessLocalColorsPending.compareAndSet(false, true)) { final long now = mClockFunction.get(); final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp; @@ -1657,80 +1674,98 @@ public abstract class WallpaperService extends Service { mHandler.postDelayed(() -> { mLastProcessLocalColorsTimestamp = now + timeToWait; mProcessLocalColorsPending.set(false); - processLocalColorsInternal(xOffset, xOffsetStep); + processLocalColorsInternal(); }, timeToWait); } } - private void processLocalColorsInternal(float xOffset, float xOffsetStep) { - // implemented by the wallpaper + /** + * Default implementation of the local color extraction. + * This will take a screenshot of the whole wallpaper on the main thread. + * Then, in a background thread, for each launcher page, for each area that needs color + * extraction in this page, creates a sub-bitmap and call {@link WallpaperColors#fromBitmap} + * to extract the colors. Every time a launcher page has been processed, call + * {@link #notifyLocalColorsChanged} with the color and areas of this page. + */ + private void processLocalColorsInternal() { if (supportsLocalColorExtraction()) return; - if (DEBUG) { - Log.d(TAG, "processLocalColors " + xOffset + " of step " - + xOffsetStep); - } - //below is the default implementation - if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN - || !mSurfaceHolder.getSurface().isValid()) return; - int xCurrentPage; + float xOffset; + float xOffsetStep; + float wallpaperDimAmount; + int xPage; int xPages; - if (!validStep(xOffsetStep)) { + Set<RectF> areas; + EngineWindowPage current; + + synchronized (mLock) { + xOffset = mPendingXOffset; + xOffsetStep = mPendingXOffsetStep; + wallpaperDimAmount = mWallpaperDimAmount; + if (DEBUG) { - Log.w(TAG, "invalid offset step " + xOffsetStep); + Log.d(TAG, "processLocalColors " + xOffset + " of step " + + xOffsetStep); + } + if (xOffset % xOffsetStep > MIN_PAGE_ALLOWED_MARGIN + || !mSurfaceHolder.getSurface().isValid()) return; + int xCurrentPage; + if (!validStep(xOffsetStep)) { + if (DEBUG) { + Log.w(TAG, "invalid offset step " + xOffsetStep); + } + xOffset = 0; + xOffsetStep = 1; + xCurrentPage = 0; + xPages = 1; + } else { + xPages = Math.round(1 / xOffsetStep) + 1; + xOffsetStep = (float) 1 / (float) xPages; + float shrink = (float) (xPages - 1) / (float) xPages; + xOffset *= shrink; + xCurrentPage = Math.round(xOffset / xOffsetStep); + } + if (DEBUG) { + Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage); + Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); } - xOffset = 0; - xOffsetStep = 1; - xCurrentPage = 0; - xPages = 1; - } else { - xPages = Math.round(1 / xOffsetStep) + 1; - xOffsetStep = (float) 1 / (float) xPages; - float shrink = (float) (xPages - 1) / (float) xPages; - xOffset *= shrink; - xCurrentPage = Math.round(xOffset / xOffsetStep); - } - if (DEBUG) { - Log.d(TAG, "xPages " + xPages + " xPage " + xCurrentPage); - Log.d(TAG, "xOffsetStep " + xOffsetStep + " xOffset " + xOffset); - } - float finalXOffsetStep = xOffsetStep; - float finalXOffset = xOffset; + float finalXOffsetStep = xOffsetStep; + float finalXOffset = xOffset; - Trace.beginSection("WallpaperService#processLocalColors"); - resetWindowPages(); - int xPage = xCurrentPage; - EngineWindowPage current; - if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) { - mWindowPages = new EngineWindowPage[xPages]; - initWindowPages(mWindowPages, finalXOffsetStep); - } - if (mLocalColorsToAdd.size() != 0) { - for (RectF colorArea : mLocalColorsToAdd) { - if (!isValid(colorArea)) continue; - mLocalColorAreas.add(colorArea); - int colorPage = getRectFPage(colorArea, finalXOffsetStep); - EngineWindowPage currentPage = mWindowPages[colorPage]; - currentPage.setLastUpdateTime(0); - currentPage.removeColor(colorArea); + resetWindowPages(); + xPage = xCurrentPage; + if (mWindowPages.length == 0 || (mWindowPages.length != xPages)) { + mWindowPages = new EngineWindowPage[xPages]; + initWindowPages(mWindowPages, finalXOffsetStep); } - mLocalColorsToAdd.clear(); - } - if (xPage >= mWindowPages.length) { - if (DEBUG) { - Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage); - Log.e(TAG, "error on page " + xPage + " out of " + xPages); - Log.e(TAG, - "error on xOffsetStep " + finalXOffsetStep - + " xOffset " + finalXOffset); + if (mLocalColorsToAdd.size() != 0) { + for (RectF colorArea : mLocalColorsToAdd) { + if (!isValid(colorArea)) continue; + mLocalColorAreas.add(colorArea); + int colorPage = getRectFPage(colorArea, finalXOffsetStep); + EngineWindowPage currentPage = mWindowPages[colorPage]; + currentPage.setLastUpdateTime(0); + currentPage.removeColor(colorArea); + } + mLocalColorsToAdd.clear(); } - xPage = mWindowPages.length - 1; + if (xPage >= mWindowPages.length) { + if (DEBUG) { + Log.e(TAG, "error xPage >= mWindowPages.length page: " + xPage); + Log.e(TAG, "error on page " + xPage + " out of " + xPages); + Log.e(TAG, + "error on xOffsetStep " + finalXOffsetStep + + " xOffset " + finalXOffset); + } + xPage = mWindowPages.length - 1; + } + current = mWindowPages[xPage]; + areas = new HashSet<>(current.getAreas()); } - current = mWindowPages[xPage]; - updatePage(current, xPage, xPages, finalXOffsetStep); - Trace.endSection(); + updatePage(current, areas, xPage, xPages, wallpaperDimAmount); } + @GuardedBy("mLock") private void initWindowPages(EngineWindowPage[] windowPages, float step) { for (int i = 0; i < windowPages.length; i++) { windowPages[i] = new EngineWindowPage(); @@ -1747,16 +1782,16 @@ public abstract class WallpaperService extends Service { } } - void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, - float xOffsetStep) { + void updatePage(EngineWindowPage currentPage, Set<RectF> areas, int pageIndx, int numPages, + float wallpaperDimAmount) { + // in case the clock is zero, we start with negative time long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION; long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots - if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) { - return; - } + if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return; + Surface surface = mSurfaceHolder.getSurface(); if (!surface.isValid()) return; boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; @@ -1769,43 +1804,59 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "wrong width and height values of bitmap " + width + " " + height); return; } + final String pixelCopySectionName = "WallpaperService#pixelCopy"; + final int pixelCopyCount = mPixelCopyCount++; + Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount); Bitmap screenShot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap finalScreenShot = screenShot; - Trace.beginSection("WallpaperService#pixelCopy"); - PixelCopy.request(surface, screenShot, (res) -> { - Trace.endSection(); - if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); - if (res != PixelCopy.SUCCESS) { - Bitmap lastBitmap = currentPage.getBitmap(); - // assign the last bitmap taken for now - currentPage.setBitmap(mLastScreenshot); - Bitmap lastScreenshot = mLastScreenshot; - if (lastScreenshot != null && !lastScreenshot.isRecycled() - && !Objects.equals(lastBitmap, lastScreenshot)) { - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); + try { + // TODO(b/274427458) check if this can be done in the background. + PixelCopy.request(surface, screenShot, (res) -> { + Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount); + if (DEBUG) { + Log.d(TAG, "result of pixel copy is: " + + (res == PixelCopy.SUCCESS ? "SUCCESS" : "FAILURE")); } - } else { - mLastScreenshot = finalScreenShot; - // going to hold this lock for a while - currentPage.setBitmap(finalScreenShot); - currentPage.setLastUpdateTime(current); - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); - } - }, mHandler); - + if (res != PixelCopy.SUCCESS) { + Bitmap lastBitmap = currentPage.getBitmap(); + // assign the last bitmap taken for now + currentPage.setBitmap(mLastScreenshot); + Bitmap lastScreenshot = mLastScreenshot; + if (lastScreenshot != null && !Objects.equals(lastBitmap, lastScreenshot)) { + updatePageColors( + currentPage, areas, pageIndx, numPages, wallpaperDimAmount); + } + } else { + mLastScreenshot = finalScreenShot; + currentPage.setBitmap(finalScreenShot); + currentPage.setLastUpdateTime(current); + updatePageColors( + currentPage, areas, pageIndx, numPages, wallpaperDimAmount); + } + }, mBackgroundHandler); + } catch (IllegalArgumentException e) { + // this can potentially happen if the surface is invalidated right between the + // surface.isValid() check and the PixelCopy operation. + // in this case, stop: we'll compute colors on the next processLocalColors call. + Log.w(TAG, "Cancelling processLocalColors: exception caught during PixelCopy"); + } } // locked by the passed page - private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages, - float xOffsetStep) { + private void updatePageColors(EngineWindowPage page, Set<RectF> areas, + int pageIndx, int numPages, float wallpaperDimAmount) { if (page.getBitmap() == null) return; + if (!mBackgroundHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException( + "ProcessLocalColors should be called from the background thread"); + } Trace.beginSection("WallpaperService#updatePageColors"); if (DEBUG) { Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas " + page.getAreas().size() + " and bitmap size of " + page.getBitmap().getWidth() + " x " + page.getBitmap().getHeight()); } - for (RectF area: page.getAreas()) { + for (RectF area: areas) { if (area == null) continue; RectF subArea = generateSubRect(area, pageIndx, numPages); Bitmap b = page.getBitmap(); @@ -1815,12 +1866,12 @@ public abstract class WallpaperService extends Service { int height = Math.round(b.getHeight() * subArea.height()); Bitmap target; try { - target = Bitmap.createBitmap(page.getBitmap(), x, y, width, height); + target = Bitmap.createBitmap(b, x, y, width, height); } catch (Exception e) { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); + WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -1837,12 +1888,14 @@ public abstract class WallpaperService extends Service { + " local color callback for area" + area + " for page " + pageIndx + " of " + numPages); } - try { - mConnection.onLocalWallpaperColorsChanged(area, color, - mDisplayContext.getDisplayId()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); - } + mHandler.post(() -> { + try { + mConnection.onLocalWallpaperColorsChanged(area, color, + mDisplayContext.getDisplayId()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); + } + }); } } Trace.endSection(); @@ -1868,16 +1921,17 @@ public abstract class WallpaperService extends Service { return new RectF(left, in.top, right, in.bottom); } + @GuardedBy("mLock") private void resetWindowPages() { if (supportsLocalColorExtraction()) return; if (!mResetWindowPages) return; mResetWindowPages = false; - mLastWindowPage = -1; for (int i = 0; i < mWindowPages.length; i++) { mWindowPages[i].setLastUpdateTime(0L); } } + @GuardedBy("mLock") private int getRectFPage(RectF area, float step) { if (!isValid(area)) return 0; if (!validStep(step)) return 0; @@ -1898,12 +1952,12 @@ public abstract class WallpaperService extends Service { if (DEBUG) { Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); } - mHandler.post(() -> { - mLocalColorsToAdd.addAll(regions); - processLocalColors(mPendingXOffset, mPendingYOffset); + mBackgroundHandler.post(() -> { + synchronized (mLock) { + mLocalColorsToAdd.addAll(regions); + } + processLocalColors(); }); - - } /** @@ -1913,16 +1967,18 @@ public abstract class WallpaperService extends Service { */ public void removeLocalColorsAreas(@NonNull List<RectF> regions) { if (supportsLocalColorExtraction()) return; - mHandler.post(() -> { - float step = mPendingXOffsetStep; - mLocalColorsToAdd.removeAll(regions); - mLocalColorAreas.removeAll(regions); - if (!validStep(step)) { - return; - } - for (int i = 0; i < mWindowPages.length; i++) { - for (int j = 0; j < regions.size(); j++) { - mWindowPages[i].removeArea(regions.get(j)); + mBackgroundHandler.post(() -> { + synchronized (mLock) { + float step = mPendingXOffsetStep; + mLocalColorsToAdd.removeAll(regions); + mLocalColorAreas.removeAll(regions); + if (!validStep(step)) { + return; + } + for (int i = 0; i < mWindowPages.length; i++) { + for (int j = 0; j < regions.size(); j++) { + mWindowPages[i].removeArea(regions.get(j)); + } } } }); @@ -1940,7 +1996,7 @@ public abstract class WallpaperService extends Service { } private boolean validStep(float step) { - return !PROHIBITED_STEPS.contains(step) && step > 0. && step <= 1.; + return !Float.isNaN(step) && step > 0f && step <= 1f; } void doCommand(WallpaperCommand cmd) { @@ -2579,6 +2635,9 @@ public abstract class WallpaperService extends Service { @Override public void onCreate() { Trace.beginSection("WPMS.onCreate"); + mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor"); + mBackgroundThread.start(); + mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); super.onCreate(); Trace.endSection(); } @@ -2591,6 +2650,7 @@ public abstract class WallpaperService extends Service { engineWrapper.destroy(); } mActiveEngines.clear(); + mBackgroundThread.quitSafely(); Trace.endSection(); } diff --git a/core/java/android/util/DataUnit.java b/core/java/android/util/DataUnit.java index cf045b8aec06..cc33af32ba93 100644 --- a/core/java/android/util/DataUnit.java +++ b/core/java/android/util/DataUnit.java @@ -36,9 +36,11 @@ public enum DataUnit { KILOBYTES { @Override public long toBytes(long v) { return v * 1_000; } }, MEGABYTES { @Override public long toBytes(long v) { return v * 1_000_000; } }, GIGABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000; } }, + TERABYTES { @Override public long toBytes(long v) { return v * 1_000_000_000_000L; } }, KIBIBYTES { @Override public long toBytes(long v) { return v * 1_024; } }, MEBIBYTES { @Override public long toBytes(long v) { return v * 1_048_576; } }, - GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } }; + GIBIBYTES { @Override public long toBytes(long v) { return v * 1_073_741_824; } }, + TEBIBYTES { @Override public long toBytes(long v) { return v * 1_099_511_627_776L; } }; public long toBytes(long v) { throw new AbstractMethodError(); diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java index cc83decc7b21..cd03d83b818c 100644 --- a/core/java/android/util/SparseArray.java +++ b/core/java/android/util/SparseArray.java @@ -71,7 +71,7 @@ public class SparseArray<E> implements Cloneable { * Creates a new SparseArray containing no mappings. */ public SparseArray() { - this(10); + this(0); } /** diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java index c145b20e6f6c..12a99004daed 100644 --- a/core/java/android/util/SparseBooleanArray.java +++ b/core/java/android/util/SparseBooleanArray.java @@ -51,7 +51,7 @@ public class SparseBooleanArray implements Cloneable { * Creates a new SparseBooleanArray containing no mappings. */ public SparseBooleanArray() { - this(10); + this(0); } /** diff --git a/core/java/android/util/SparseDoubleArray.java b/core/java/android/util/SparseDoubleArray.java index ee2e3ce3e0ee..4b0cbe45d857 100644 --- a/core/java/android/util/SparseDoubleArray.java +++ b/core/java/android/util/SparseDoubleArray.java @@ -50,7 +50,7 @@ public class SparseDoubleArray implements Cloneable { /** Creates a new SparseDoubleArray containing no mappings. */ public SparseDoubleArray() { - this(10); + this(0); } /** diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java index d4f6685e4347..0e98c285c227 100644 --- a/core/java/android/util/SparseIntArray.java +++ b/core/java/android/util/SparseIntArray.java @@ -58,7 +58,7 @@ public class SparseIntArray implements Cloneable { * Creates a new SparseIntArray containing no mappings. */ public SparseIntArray() { - this(10); + this(0); } /** diff --git a/core/java/android/util/SparseLongArray.java b/core/java/android/util/SparseLongArray.java index b739e379499c..e86b6472dc39 100644 --- a/core/java/android/util/SparseLongArray.java +++ b/core/java/android/util/SparseLongArray.java @@ -51,7 +51,7 @@ public class SparseLongArray implements Cloneable { * Creates a new SparseLongArray containing no mappings. */ public SparseLongArray() { - this(10); + this(0); } /** diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 9225cd908c17..61864d776cab 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -1183,8 +1183,10 @@ public final class InputDevice implements Parcelable { */ @NonNull public LightsManager getLightsManager() { - if (mLightsManager == null) { - mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId); + synchronized (mMotionRanges) { + if (mLightsManager == null) { + mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId); + } } return mLightsManager; } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index b46a68c1d5fd..d8b6b7b25a24 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -851,10 +851,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); - if (mViewVisibility) { - surfaceUpdateTransaction.show(mSurfaceControl); - } else { - surfaceUpdateTransaction.hide(mSurfaceControl); + // Only control visibility if we're not hardware-accelerated. Otherwise we'll + // let renderthread drive since offscreen SurfaceControls should not be visible. + if (!isHardwareAccelerated()) { + if (mViewVisibility) { + surfaceUpdateTransaction.show(mSurfaceControl); + } else { + surfaceUpdateTransaction.hide(mSurfaceControl); + } } updateBackgroundVisibility(surfaceUpdateTransaction); @@ -1417,12 +1421,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private final Rect mRTLastReportedPosition = new Rect(); - private final Point mRTLastReportedSurfaceSize = new Point(); private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener { private final int mRtSurfaceWidth; private final int mRtSurfaceHeight; - private boolean mRtFirst = true; private final SurfaceControl.Transaction mPositionChangedTransaction = new SurfaceControl.Transaction(); @@ -1433,15 +1435,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override public void positionChanged(long frameNumber, int left, int top, int right, int bottom) { - if (!mRtFirst && (mRTLastReportedPosition.left == left - && mRTLastReportedPosition.top == top - && mRTLastReportedPosition.right == right - && mRTLastReportedPosition.bottom == bottom - && mRTLastReportedSurfaceSize.x == mRtSurfaceWidth - && mRTLastReportedSurfaceSize.y == mRtSurfaceHeight)) { - return; - } - mRtFirst = false; try { if (DEBUG_POSITION) { Log.d(TAG, String.format( @@ -1452,8 +1445,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } synchronized (mSurfaceControlLock) { if (mSurfaceControl == null) return; + mRTLastReportedPosition.set(left, top, right, bottom); - mRTLastReportedSurfaceSize.set(mRtSurfaceWidth, mRtSurfaceHeight); onSetSurfacePositionAndScale(mPositionChangedTransaction, mSurfaceControl, mRTLastReportedPosition.left /*positionLeft*/, mRTLastReportedPosition.top /*positionTop*/, @@ -1461,10 +1454,8 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall / (float) mRtSurfaceWidth /*postScaleX*/, mRTLastReportedPosition.height() / (float) mRtSurfaceHeight /*postScaleY*/); - if (mViewVisibility) { - // b/131239825 - mPositionChangedTransaction.show(mSurfaceControl); - } + + mPositionChangedTransaction.show(mSurfaceControl); } applyOrMergeTransaction(mPositionChangedTransaction, frameNumber); } catch (Exception ex) { @@ -1490,7 +1481,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall System.identityHashCode(this), frameNumber)); } mRTLastReportedPosition.setEmpty(); - mRTLastReportedSurfaceSize.set(-1, -1); // positionLost can be called while UI thread is un-paused. synchronized (mSurfaceControlLock) { diff --git a/core/java/android/widget/OWNERS b/core/java/android/widget/OWNERS index 02dafd44d3bd..7f0a651c6420 100644 --- a/core/java/android/widget/OWNERS +++ b/core/java/android/widget/OWNERS @@ -13,3 +13,5 @@ per-file TextView*,Edit*,Selection* = file:../text/OWNERS per-file SpellChecker.java = file:../view/inputmethod/OWNERS per-file RemoteViews* = file:../appwidget/OWNERS + +per-file Toast.java = juliacr@google.com, jeffdq@google.com diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS index 0d02683868c5..a1d571fc4350 100644 --- a/core/java/com/android/internal/app/OWNERS +++ b/core/java/com/android/internal/app/OWNERS @@ -1,4 +1,6 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS +per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS +per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS per-file *Resolver* = file:/packages/SystemUI/OWNERS per-file *Chooser* = file:/packages/SystemUI/OWNERS per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java index 9fae2113f0f9..2b08a5525d82 100644 --- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java +++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java @@ -74,6 +74,9 @@ public class SystemUiSystemPropertiesFlags { public static final Flag OTP_REDACTION = devFlag("persist.sysui.notification.otp_redaction"); + /** Gating the removal of sorting-notifications-by-interruptiveness. */ + public static final Flag NO_SORT_BY_INTERRUPTIVENESS = + devFlag("persist.sysui.notification.no_sort_by_interruptiveness"); } //// == End of flags. Everything below this line is the implementation. == //// diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java index afdbdc8ead73..4a46d91efbf0 100644 --- a/core/java/com/android/internal/expresslog/Counter.java +++ b/core/java/com/android/internal/expresslog/Counter.java @@ -36,6 +36,16 @@ public final class Counter { } /** + * Increments Telemetry Express Counter metric by 1 + * @param metricId to log, no-op if metricId is not defined in the TeX catalog + * @param uid used as a dimension for the count metric + * @hide + */ + public static void logIncrementWithUid(@NonNull String metricId, int uid) { + logIncrementWithUid(metricId, uid, 1); + } + + /** * Increments Telemetry Express Counter metric by arbitrary value * @param metricId to log, no-op if metricId is not defined in the TeX catalog * @param amount to increment counter @@ -45,4 +55,17 @@ public final class Counter { final long metricIdHash = Utils.hashString(metricId); FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount); } + + /** + * Increments Telemetry Express Counter metric by arbitrary value + * @param metricId to log, no-op if metricId is not defined in the TeX catalog + * @param uid used as a dimension for the count metric + * @param amount to increment counter + * @hide + */ + public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) { + final long metricIdHash = Utils.hashString(metricId); + FrameworkStatsLog.write( + FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid); + } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 1505ccce97a1..85cb15bdd906 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -721,7 +721,8 @@ public class ZygoteInit { } catch (ErrnoException ex) { throw new RuntimeException("Failed to capget()", ex); } - capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32); + capabilities &= Integer.toUnsignedLong(data[0].effective) | + (Integer.toUnsignedLong(data[1].effective) << 32); /* Hardcoded command line to start the system server */ String[] args = { diff --git a/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS new file mode 100644 index 000000000000..77550002e3c6 --- /dev/null +++ b/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS @@ -0,0 +1,3 @@ +# TODO(b/274465475): Migrate LatencyTracker testing to its own module +marcinoc@google.com +ilkos@google.com diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java index 43a9f5f62e65..3c0675504e25 100644 --- a/core/java/com/android/internal/util/LatencyTracker.java +++ b/core/java/com/android/internal/util/LatencyTracker.java @@ -48,6 +48,7 @@ import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_I import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX; import android.Manifest; +import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -55,7 +56,6 @@ import android.annotation.RequiresPermission; import android.app.ActivityThread; import android.content.Context; import android.os.Build; -import android.os.ConditionVariable; import android.os.SystemClock; import android.os.Trace; import android.provider.DeviceConfig; @@ -79,7 +79,7 @@ import java.util.concurrent.TimeUnit; * Class to track various latencies in SystemUI. It then writes the latency to statsd and also * outputs it to logcat so these latencies can be captured by tests and then used for dashboards. * <p> - * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but + * This is currently only in Keyguard. It can be shared between SystemUI and Keyguard, but * eventually we'd want to merge these two packages together so Keyguard can use common classes * that are shared with SystemUI. */ @@ -285,8 +285,6 @@ public class LatencyTracker { UIACTION_LATENCY_REPORTED__ACTION__ACTION_REQUEST_IME_HIDDEN, }; - private static LatencyTracker sLatencyTracker; - private final Object mLock = new Object(); @GuardedBy("mLock") private final SparseArray<Session> mSessions = new SparseArray<>(); @@ -294,20 +292,21 @@ public class LatencyTracker { private final SparseArray<ActionProperties> mActionPropertiesMap = new SparseArray<>(); @GuardedBy("mLock") private boolean mEnabled; - @VisibleForTesting - public final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable(); + + // Wrapping this in a holder class achieves lazy loading behavior + private static final class SLatencyTrackerHolder { + private static final LatencyTracker sLatencyTracker = new LatencyTracker(); + } public static LatencyTracker getInstance(Context context) { - if (sLatencyTracker == null) { - synchronized (LatencyTracker.class) { - if (sLatencyTracker == null) { - sLatencyTracker = new LatencyTracker(); - } - } - } - return sLatencyTracker; + return SLatencyTrackerHolder.sLatencyTracker; } + /** + * Constructor for LatencyTracker + * + * <p>This constructor is only visible for test classes to inject their own consumer callbacks + */ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) @VisibleForTesting public LatencyTracker() { @@ -349,11 +348,8 @@ public class LatencyTracker { properties.getInt(actionName + TRACE_THRESHOLD_SUFFIX, legacyActionTraceThreshold))); } - if (DEBUG) { - Log.d(TAG, "updated action properties: " + mActionPropertiesMap); - } + onDeviceConfigPropertiesUpdated(mActionPropertiesMap); } - mDeviceConfigPropertiesUpdated.open(); } /** @@ -477,7 +473,7 @@ public class LatencyTracker { */ public void onActionStart(@Action int action, String tag) { synchronized (mLock) { - if (!isEnabled()) { + if (!isEnabled(action)) { return; } // skip if the action is already instrumenting. @@ -501,7 +497,7 @@ public class LatencyTracker { */ public void onActionEnd(@Action int action) { synchronized (mLock) { - if (!isEnabled()) { + if (!isEnabled(action)) { return; } Session session = mSessions.get(action); @@ -540,6 +536,24 @@ public class LatencyTracker { } /** + * Testing API to get the time when a given action was started. + * + * @param action Action which to retrieve start time from + * @return Elapsed realtime timestamp when the action started. -1 if the action is not active. + * @hide + */ + @VisibleForTesting + @ElapsedRealtimeLong + public long getActiveActionStartTime(@Action int action) { + synchronized (mLock) { + if (mSessions.contains(action)) { + return mSessions.get(action).mStartRtc; + } + return -1; + } + } + + /** * Logs an action that has started and ended. This needs to be called from the main thread. * * @param action The action to end. One of the ACTION_* values. @@ -549,6 +563,9 @@ public class LatencyTracker { boolean shouldSample; int traceThreshold; synchronized (mLock) { + if (!isEnabled(action)) { + return; + } ActionProperties actionProperties = mActionPropertiesMap.get(action); if (actionProperties == null) { return; @@ -559,28 +576,24 @@ public class LatencyTracker { traceThreshold = actionProperties.getTraceThreshold(); } - if (traceThreshold > 0 && duration >= traceThreshold) { - PerfettoTrigger.trigger(getTraceTriggerNameForAction(action)); - } + boolean shouldTriggerPerfettoTrace = traceThreshold > 0 && duration >= traceThreshold; - logActionDeprecated(action, duration, shouldSample); - } + if (DEBUG) { + Log.i(TAG, "logAction: " + getNameOfAction(STATSD_ACTION[action]) + + " duration=" + duration + + " shouldSample=" + shouldSample + + " shouldTriggerPerfettoTrace=" + shouldTriggerPerfettoTrace); + } - /** - * Logs an action that has started and ended. This needs to be called from the main thread. - * - * @param action The action to end. One of the ACTION_* values. - * @param duration The duration of the action in ms. - * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog. - */ - public static void logActionDeprecated( - @Action int action, int duration, boolean writeToStatsLog) { - Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration); EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration); - - if (writeToStatsLog) { - FrameworkStatsLog.write( - FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration); + if (shouldTriggerPerfettoTrace) { + onTriggerPerfetto(getTraceTriggerNameForAction(action)); + } + if (shouldSample) { + onLogToFrameworkStats( + new FrameworkStatsLogEvent(action, FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, + STATSD_ACTION[action], duration) + ); } } @@ -642,10 +655,10 @@ public class LatencyTracker { } @VisibleForTesting - static class ActionProperties { + public static class ActionProperties { static final String ENABLE_SUFFIX = "_enable"; static final String SAMPLE_INTERVAL_SUFFIX = "_sample_interval"; - // TODO: migrate all usages of the legacy trace theshold property + // TODO: migrate all usages of the legacy trace threshold property static final String LEGACY_TRACE_THRESHOLD_SUFFIX = ""; static final String TRACE_THRESHOLD_SUFFIX = "_trace_threshold"; @@ -655,7 +668,8 @@ public class LatencyTracker { private final int mSamplingInterval; private final int mTraceThreshold; - ActionProperties( + @VisibleForTesting + public ActionProperties( @Action int action, boolean enabled, int samplingInterval, @@ -668,20 +682,24 @@ public class LatencyTracker { this.mTraceThreshold = traceThreshold; } + @VisibleForTesting @Action - int getAction() { + public int getAction() { return mAction; } - boolean isEnabled() { + @VisibleForTesting + public boolean isEnabled() { return mEnabled; } - int getSamplingInterval() { + @VisibleForTesting + public int getSamplingInterval() { return mSamplingInterval; } - int getTraceThreshold() { + @VisibleForTesting + public int getTraceThreshold() { return mTraceThreshold; } @@ -694,5 +712,103 @@ public class LatencyTracker { + ", mTraceThreshold=" + mTraceThreshold + "}"; } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null) { + return false; + } + if (!(o instanceof ActionProperties)) { + return false; + } + ActionProperties that = (ActionProperties) o; + return mAction == that.mAction + && mEnabled == that.mEnabled + && mSamplingInterval == that.mSamplingInterval + && mTraceThreshold == that.mTraceThreshold; + } + + @Override + public int hashCode() { + int _hash = 1; + _hash = 31 * _hash + mAction; + _hash = 31 * _hash + Boolean.hashCode(mEnabled); + _hash = 31 * _hash + mSamplingInterval; + _hash = 31 * _hash + mTraceThreshold; + return _hash; + } + } + + /** + * Testing method intended to be overridden to determine when the LatencyTracker's device + * properties are updated. + */ + @VisibleForTesting + public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) { + if (DEBUG) { + Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties); + } + } + + /** + * Testing class intended to be overridden to determine when LatencyTracker triggers perfetto. + */ + @VisibleForTesting + public void onTriggerPerfetto(String triggerName) { + if (DEBUG) { + Log.i(TAG, "onTriggerPerfetto: triggerName=" + triggerName); + } + PerfettoTrigger.trigger(triggerName); + } + + /** + * Testing method intended to be overridden to determine when LatencyTracker writes to + * FrameworkStatsLog. + */ + @VisibleForTesting + public void onLogToFrameworkStats(FrameworkStatsLogEvent event) { + if (DEBUG) { + Log.i(TAG, "onLogToFrameworkStats: event=" + event); + } + FrameworkStatsLog.write(event.logCode, event.statsdAction, event.durationMillis); + } + + /** + * Testing class intended to reject what should be written to the {@link FrameworkStatsLog} + * + * <p>This class is used in {@link #onLogToFrameworkStats(FrameworkStatsLogEvent)} for test code + * to observer when and what information is being logged by {@link LatencyTracker} + */ + @VisibleForTesting + public static class FrameworkStatsLogEvent { + + @VisibleForTesting + public final int action; + @VisibleForTesting + public final int logCode; + @VisibleForTesting + public final int statsdAction; + @VisibleForTesting + public final int durationMillis; + + private FrameworkStatsLogEvent(int action, int logCode, int statsdAction, + int durationMillis) { + this.action = action; + this.logCode = logCode; + this.statsdAction = statsdAction; + this.durationMillis = durationMillis; + } + + @Override + public String toString() { + return "FrameworkStatsLogEvent{" + + " logCode=" + logCode + + ", statsdAction=" + statsdAction + + ", durationMillis=" + durationMillis + + "}"; + } } } diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS index 1808bd56b562..9be8ea7aadc4 100644 --- a/core/java/com/android/internal/util/OWNERS +++ b/core/java/com/android/internal/util/OWNERS @@ -6,3 +6,4 @@ per-file Protocol* = etancohen@google.com, lorenzo@google.com per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com per-file *Dump* = file:/core/java/com/android/internal/util/dump/OWNERS per-file *Screenshot* = file:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS +per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 092f6e50aea1..0f1e558c98e1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4482,6 +4482,12 @@ <permission android:name="android.permission.PROVIDE_DEFAULT_ENABLED_CREDENTIAL_SERVICE" android:protectionLevel="signature|privileged" /> + <!-- Allows specifying candidate credential providers to be queried in Credential Manager + get flows, or to be preferred as a default in the Credential Manager create flows. + <p>Protection level: normal --> + <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS" + android:protectionLevel="normal" /> + <!-- Allows a browser to invoke credential manager APIs on behalf of another RP. <p>Protection level: normal --> <permission android:name="android.permission.CREDENTIAL_MANAGER_SET_ORIGIN" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 41c8b4ab153b..b53502d2a666 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -10089,4 +10089,27 @@ <!-- Maximum width of height drawable. Drawables exceeding this size will be downsampled. --> <attr name="maxDrawableHeight" format="dimension"/> </declare-styleable> + + <!-- =============================== --> + <!-- Credential Manager attributes --> + <!-- =============================== --> + <eat-comment /> + + <!-- Contains Credential Provider related metadata. Since providers are exposed + as services these should live under the service. + --> + <declare-styleable name="CredentialProvider"> + <!-- A string that is displayed to the user in the Credential Manager settings + screen that can be used to provide more information about a provider. For + longer strings (40 char) it will be truncated. If multiple services + show the subtitle then the string will be joined together. --> + <attr name="settingsSubtitle" format="string" /> + </declare-styleable> + + <!-- A list of capabilities that indicates to the OS what kinds of credentials + this provider supports. This list is defined in CredentialProviderService. --> + <declare-styleable name="CredentialProvider_Capabilities" parent="CredentialProvider"> + <!-- An individual capability declared by the provider. --> + <attr name="capability" format="string" /> + </declare-styleable> </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 696c0ed0530a..12a6c5262f05 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -130,6 +130,8 @@ <public name="focusedSearchResultHighlightColor" /> <public name="stylusHandwritingSettingsActivity" /> <public name="windowNoMoveAnimation" /> + <public name="settingsSubtitle" /> + <public name="capability" /> </staging-public-group> <staging-public-group type="id" first-id="0x01cd0000"> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index e811bb67fb99..3ea15924e96d 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -20,6 +20,7 @@ android_test { "BinderProxyCountingTestService/src/**/*.java", "BinderDeathRecipientHelperApp/src/**/*.java", "aidl/**/I*.aidl", + ":FrameworksCoreTestDoubles-sources", ], aidl: { diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 34d669bb5ebd..6debbfeae084 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -63,7 +63,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.annotation.Nullable; -import android.app.Notification.CallStyle; import android.content.Context; import android.content.Intent; import android.content.LocusId; @@ -1039,6 +1038,69 @@ public class NotificationTest { } @Test + public void areIconsDifferent_sameSmallIcon_false() { + Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build(); + Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(1).build(); + + assertThat(Notification.areIconsDifferent(n1, n2)).isFalse(); + } + + @Test + public void areIconsDifferent_differentSmallIcon_true() { + Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build(); + Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build(); + + assertThat(Notification.areIconsDifferent(n1, n2)).isTrue(); + } + + @Test + public void areIconsDifferent_sameLargeIcon_false() { + Icon icon1 = Icon.createWithContentUri("uri"); + Icon icon2 = Icon.createWithContentUri("uri"); + Notification n1 = new Notification.Builder(mContext, "test") + .setSmallIcon(1).setLargeIcon(icon1).build(); + Notification n2 = new Notification.Builder(mContext, "test") + .setSmallIcon(1).setLargeIcon(icon2).build(); + + // Note that this will almost certainly not happen for Icons created from Bitmaps, since + // their serialization/deserialization of Bitmaps (app -> system_process) results in a + // different getGenerationId() value. :( + assertThat(Notification.areIconsDifferent(n1, n2)).isFalse(); + } + + @Test + public void areIconsDifferent_differentLargeIcon_true() { + Icon icon1 = Icon.createWithContentUri("uri1"); + Icon icon2 = Icon.createWithContentUri("uri2"); + Notification n1 = new Notification.Builder(mContext, "test") + .setSmallIcon(1).setLargeIcon(icon1).build(); + Notification n2 = new Notification.Builder(mContext, "test") + .setSmallIcon(2).setLargeIcon(icon2).build(); + + assertThat(Notification.areIconsDifferent(n1, n2)).isTrue(); + } + + @Test + public void areIconsDifferent_addedLargeIcon_true() { + Icon icon = Icon.createWithContentUri("uri"); + Notification n1 = new Notification.Builder(mContext, "test").setSmallIcon(1).build(); + Notification n2 = new Notification.Builder(mContext, "test") + .setSmallIcon(2).setLargeIcon(icon).build(); + + assertThat(Notification.areIconsDifferent(n1, n2)).isTrue(); + } + + @Test + public void areIconsDifferent_removedLargeIcon_true() { + Icon icon = Icon.createWithContentUri("uri"); + Notification n1 = new Notification.Builder(mContext, "test") + .setSmallIcon(1).setLargeIcon(icon).build(); + Notification n2 = new Notification.Builder(mContext, "test").setSmallIcon(2).build(); + + assertThat(Notification.areIconsDifferent(n1, n2)).isTrue(); + } + + @Test public void testStyleChangeVisiblyDifferent_noStyles() { Notification.Builder n1 = new Notification.Builder(mContext, "test"); Notification.Builder n2 = new Notification.Builder(mContext, "test"); diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index 91fbe0034133..394ff0ae9a2e 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -54,6 +54,7 @@ import static org.junit.Assert.fail; import android.content.Context; import android.os.FileUtils.MemoryPipe; import android.provider.DocumentsContract.Document; +import android.util.DataUnit; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -504,31 +505,45 @@ public class FileUtilsTest { @Test public void testRoundStorageSize() throws Exception { - final long M128 = 128000000L; - final long M256 = 256000000L; - final long M512 = 512000000L; - final long G1 = 1000000000L; - final long G2 = 2000000000L; - final long G16 = 16000000000L; - final long G32 = 32000000000L; - final long G64 = 64000000000L; - - assertEquals(M128, roundStorageSize(M128)); - assertEquals(M256, roundStorageSize(M128 + 1)); - assertEquals(M256, roundStorageSize(M256 - 1)); - assertEquals(M256, roundStorageSize(M256)); - assertEquals(M512, roundStorageSize(M256 + 1)); - assertEquals(M512, roundStorageSize(M512 - 1)); - assertEquals(M512, roundStorageSize(M512)); - assertEquals(G1, roundStorageSize(M512 + 1)); - assertEquals(G1, roundStorageSize(G1)); - assertEquals(G2, roundStorageSize(G1 + 1)); - - assertEquals(G16, roundStorageSize(G16)); - assertEquals(G32, roundStorageSize(G16 + 1)); - assertEquals(G32, roundStorageSize(G32 - 1)); - assertEquals(G32, roundStorageSize(G32)); - assertEquals(G64, roundStorageSize(G32 + 1)); + final long GB1 = DataUnit.GIGABYTES.toBytes(1); + final long GiB1 = DataUnit.GIBIBYTES.toBytes(1); + final long GB2 = DataUnit.GIGABYTES.toBytes(2); + final long GiB2 = DataUnit.GIBIBYTES.toBytes(2); + final long GiB128 = DataUnit.GIBIBYTES.toBytes(128); + final long GB256 = DataUnit.GIGABYTES.toBytes(256); + final long GiB256 = DataUnit.GIBIBYTES.toBytes(256); + final long GB512 = DataUnit.GIGABYTES.toBytes(512); + final long GiB512 = DataUnit.GIBIBYTES.toBytes(512); + final long TB1 = DataUnit.TERABYTES.toBytes(1); + final long TiB1 = DataUnit.TEBIBYTES.toBytes(1); + final long TB2 = DataUnit.TERABYTES.toBytes(2); + final long TiB2 = DataUnit.TEBIBYTES.toBytes(2); + final long TB4 = DataUnit.TERABYTES.toBytes(4); + final long TiB4 = DataUnit.TEBIBYTES.toBytes(4); + final long TB8 = DataUnit.TERABYTES.toBytes(8); + final long TiB8 = DataUnit.TEBIBYTES.toBytes(8); + + assertEquals(GB1, roundStorageSize(GB1 - 1)); + assertEquals(GB1, roundStorageSize(GB1)); + assertEquals(GB1, roundStorageSize(GB1 + 1)); + assertEquals(GB1, roundStorageSize(GiB1 - 1)); + assertEquals(GB1, roundStorageSize(GiB1)); + assertEquals(GB2, roundStorageSize(GiB1 + 1)); + assertEquals(GB2, roundStorageSize(GiB2)); + + assertEquals(GB256, roundStorageSize(GiB128 + 1)); + assertEquals(GB256, roundStorageSize(GiB256)); + assertEquals(GB512, roundStorageSize(GiB256 + 1)); + assertEquals(GB512, roundStorageSize(GiB512)); + assertEquals(TB1, roundStorageSize(GiB512 + 1)); + assertEquals(TB1, roundStorageSize(TiB1)); + assertEquals(TB2, roundStorageSize(TiB1 + 1)); + assertEquals(TB2, roundStorageSize(TiB2)); + assertEquals(TB4, roundStorageSize(TiB2 + 1)); + assertEquals(TB4, roundStorageSize(TiB4)); + assertEquals(TB8, roundStorageSize(TiB4 + 1)); + assertEquals(TB8, roundStorageSize(TiB8)); + assertEquals(TB1, roundStorageSize(1013077688320L)); // b/268571529 } @Test diff --git a/core/tests/coretests/src/android/util/DataUnitTest.java b/core/tests/coretests/src/android/util/DataUnitTest.java index ec296b76c374..034cbddc0144 100644 --- a/core/tests/coretests/src/android/util/DataUnitTest.java +++ b/core/tests/coretests/src/android/util/DataUnitTest.java @@ -26,11 +26,13 @@ public class DataUnitTest extends TestCase { assertEquals(12_000L, DataUnit.KILOBYTES.toBytes(12)); assertEquals(12_000_000L, DataUnit.MEGABYTES.toBytes(12)); assertEquals(12_000_000_000L, DataUnit.GIGABYTES.toBytes(12)); + assertEquals(12_000_000_000_000L, DataUnit.TERABYTES.toBytes(12)); } public void testIec() throws Exception { assertEquals(12_288L, DataUnit.KIBIBYTES.toBytes(12)); assertEquals(12_582_912L, DataUnit.MEBIBYTES.toBytes(12)); assertEquals(12_884_901_888L, DataUnit.GIBIBYTES.toBytes(12)); + assertEquals(13_194_139_533_312L, DataUnit.TEBIBYTES.toBytes(12)); } } diff --git a/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java new file mode 100644 index 000000000000..e6f10adacee3 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/FakeLatencyTrackerTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import static com.android.internal.util.FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION; +import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED; +import static com.android.internal.util.LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.List; + +/** + * This test class verifies the additional methods which {@link FakeLatencyTracker} exposes. + * + * <p>The typical {@link LatencyTracker} behavior test coverage is present in + * {@link LatencyTrackerTest} + */ +@RunWith(AndroidJUnit4.class) +public class FakeLatencyTrackerTest { + + private FakeLatencyTracker mFakeLatencyTracker; + + @Before + public void setUp() throws Exception { + mFakeLatencyTracker = FakeLatencyTracker.create(); + } + + @Test + public void testForceEnabled() throws Exception { + mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234); + + assertThat(mFakeLatencyTracker.getEventsWrittenToFrameworkStats( + ACTION_SHOW_VOICE_INTERACTION)).isEmpty(); + + mFakeLatencyTracker.forceEnabled(ACTION_SHOW_VOICE_INTERACTION, 1000); + mFakeLatencyTracker.logAction(ACTION_SHOW_VOICE_INTERACTION, 1234); + List<LatencyTracker.FrameworkStatsLogEvent> events = + mFakeLatencyTracker.getEventsWrittenToFrameworkStats( + ACTION_SHOW_VOICE_INTERACTION); + assertThat(events).hasSize(1); + assertThat(events.get(0).logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED); + assertThat(events.get(0).statsdAction).isEqualTo( + UIACTION_LATENCY_REPORTED__ACTION__ACTION_SHOW_VOICE_INTERACTION); + assertThat(events.get(0).durationMillis).isEqualTo(1234); + } +} diff --git a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java index d1f0b5e1dd30..645324d57ea9 100644 --- a/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/util/LatencyTrackerTest.java @@ -16,19 +16,22 @@ package com.android.internal.util; +import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER; import static android.text.TextUtils.formatSimple; +import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED; import static com.android.internal.util.LatencyTracker.STATSD_ACTION; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.provider.DeviceConfig; -import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.internal.util.LatencyTracker.ActionProperties; + import com.google.common.truth.Expect; import org.junit.Before; @@ -38,7 +41,6 @@ import org.junit.runner.RunWith; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.time.Duration; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; @@ -49,27 +51,23 @@ import java.util.stream.Collectors; @SmallTest @RunWith(AndroidJUnit4.class) public class LatencyTrackerTest { - private static final String TAG = LatencyTrackerTest.class.getSimpleName(); private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__"; - private static final String ACTION_ENABLE_SUFFIX = "_enable"; - private static final Duration TEST_TIMEOUT = Duration.ofMillis(500); @Rule public final Expect mExpect = Expect.create(); + // Fake is used because it tests the real logic of LatencyTracker, and it only fakes the + // outcomes (PerfettoTrigger and FrameworkStatsLog). + private FakeLatencyTracker mLatencyTracker; + @Before - public void setUp() { - DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, - LatencyTracker.SETTINGS_ENABLED_KEY); - getAllActions().forEach(action -> { - DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, - action.getName().toLowerCase() + ACTION_ENABLE_SUFFIX); - }); + public void setUp() throws Exception { + mLatencyTracker = FakeLatencyTracker.create(); } @Test public void testCujsMapToEnumsCorrectly() { - List<Field> actions = getAllActions(); + List<Field> actions = getAllActionFields(); Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) && Modifier.isStatic(f.getModifiers()) @@ -101,7 +99,7 @@ public class LatencyTrackerTest { @Test public void testCujTypeEnumCorrectlyDefined() throws Exception { - List<Field> cujEnumFields = getAllActions(); + List<Field> cujEnumFields = getAllActionFields(); HashSet<Integer> allValues = new HashSet<>(); for (Field field : cujEnumFields) { int fieldValue = field.getInt(null); @@ -118,92 +116,242 @@ public class LatencyTrackerTest { } @Test - public void testIsEnabled_globalEnabled() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, + public void testIsEnabled_trueWhenGlobalEnabled() throws Exception { + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, LatencyTracker.SETTINGS_ENABLED_KEY, "true", false); - LatencyTracker latencyTracker = new LatencyTracker(); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat(latencyTracker.isEnabled()).isTrue(); + mLatencyTracker.waitForGlobalEnabledState(true); + mLatencyTracker.waitForAllPropertiesEnableState(true); + + //noinspection deprecation + assertThat(mLatencyTracker.isEnabled()).isTrue(); } @Test - public void testIsEnabled_globalDisabled() { - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, + public void testIsEnabled_falseWhenGlobalDisabled() throws Exception { + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, LatencyTracker.SETTINGS_ENABLED_KEY, "false", false); - LatencyTracker latencyTracker = new LatencyTracker(); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat(latencyTracker.isEnabled()).isFalse(); + mLatencyTracker.waitForGlobalEnabledState(false); + mLatencyTracker.waitForAllPropertiesEnableState(false); + + //noinspection deprecation + assertThat(mLatencyTracker.isEnabled()).isFalse(); } @Test - public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() { - LatencyTracker latencyTracker = new LatencyTracker(); + public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet() + throws Exception { // using a single test action, but this applies to all actions int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; - Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY + ", value=true"); - latencyTracker.mDeviceConfigPropertiesUpdated.close(); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, + DeviceConfig.deleteProperty(NAMESPACE_LATENCY_TRACKER, + "action_show_voice_interaction_enable"); + mLatencyTracker.waitForAllPropertiesEnableState(false); + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, LatencyTracker.SETTINGS_ENABLED_KEY, "true", false); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat( - latencyTracker.isEnabled(action)).isTrue(); - - Log.i(TAG, "setting property=" + LatencyTracker.SETTINGS_ENABLED_KEY - + ", value=false"); - latencyTracker.mDeviceConfigPropertiesUpdated.close(); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_LATENCY_TRACKER, - LatencyTracker.SETTINGS_ENABLED_KEY, "false", false); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat(latencyTracker.isEnabled(action)).isFalse(); + mLatencyTracker.waitForGlobalEnabledState(true); + mLatencyTracker.waitForAllPropertiesEnableState(true); + + assertThat(mLatencyTracker.isEnabled(action)).isTrue(); } @Test public void testIsEnabledAction_actionPropertyOverridesGlobalProperty() - throws DeviceConfig.BadConfigException { - LatencyTracker latencyTracker = new LatencyTracker(); + throws Exception { // using a single test action, but this applies to all actions int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; - String actionEnableProperty = "action_show_voice_interaction" + ACTION_ENABLE_SUFFIX; - Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true"); - - latencyTracker.mDeviceConfigPropertiesUpdated.close(); - Map<String, String> properties = new HashMap<String, String>() {{ - put(LatencyTracker.SETTINGS_ENABLED_KEY, "false"); - put(actionEnableProperty, "true"); - }}; + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, + LatencyTracker.SETTINGS_ENABLED_KEY, "false", false); + mLatencyTracker.waitForGlobalEnabledState(false); + + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "true"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1"); DeviceConfig.setProperties( - new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, - properties)); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat(latencyTracker.isEnabled(action)).isTrue(); - - latencyTracker.mDeviceConfigPropertiesUpdated.close(); - Log.i(TAG, "setting property=" + actionEnableProperty + ", value=false"); - properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "true"); - properties.put(actionEnableProperty, "false"); + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + -1 /* traceThreshold */)); + + assertThat(mLatencyTracker.isEnabled(action)).isTrue(); + } + + @Test + public void testLogsWhenEnabled() throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "true"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1"); DeviceConfig.setProperties( - new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, - properties)); - waitForLatencyTrackerToUpdateProperties(latencyTracker); - assertThat(latencyTracker.isEnabled(action)).isFalse(); + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + -1 /* traceThreshold */)); + + mLatencyTracker.logAction(action, 1234); + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1); + LatencyTracker.FrameworkStatsLogEvent frameworkStatsLog = + mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0); + assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED); + assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]); + assertThat(frameworkStatsLog.durationMillis).isEqualTo(1234); + + mLatencyTracker.clearEvents(); + + mLatencyTracker.onActionStart(action); + mLatencyTracker.onActionEnd(action); + // assert that action was logged, but we cannot confirm duration logged + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1); + frameworkStatsLog = mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0); + assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED); + assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]); } - private void waitForLatencyTrackerToUpdateProperties(LatencyTracker latencyTracker) { - try { - Thread.sleep(TEST_TIMEOUT.toMillis()); - } catch (InterruptedException e) { - e.printStackTrace(); - } - assertThat(latencyTracker.mDeviceConfigPropertiesUpdated.block( - TEST_TIMEOUT.toMillis())).isTrue(); + @Test + public void testDoesNotLogWhenDisabled() throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable", + "false", false); + mLatencyTracker.waitForActionEnabledState(action, false); + assertThat(mLatencyTracker.isEnabled(action)).isFalse(); + + mLatencyTracker.logAction(action, 1234); + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty(); + + mLatencyTracker.onActionStart(action); + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty(); + } + + @Test + public void testOnActionEndDoesNotLogWithoutOnActionStart() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable", + "true", false); + mLatencyTracker.waitForActionEnabledState(action, true); + assertThat(mLatencyTracker.isEnabled(action)).isTrue(); + + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty(); + } + + @Test + public void testOnActionEndDoesNotLogWhenCanceled() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable", + "true", false); + mLatencyTracker.waitForActionEnabledState(action, true); + assertThat(mLatencyTracker.isEnabled(action)).isTrue(); + + mLatencyTracker.onActionStart(action); + mLatencyTracker.onActionCancel(action); + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty(); + } + + @Test + public void testNeverTriggersPerfettoWhenThresholdNegative() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "true"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1"); + DeviceConfig.setProperties( + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + -1 /* traceThreshold */)); + + mLatencyTracker.onActionStart(action); + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty(); + } + + @Test + public void testNeverTriggersPerfettoWhenDisabled() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "false"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1"); + DeviceConfig.setProperties( + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, false /* enabled */, 1 /* samplingInterval */, + 1 /* traceThreshold */)); + + mLatencyTracker.onActionStart(action); + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty(); + } + + @Test + public void testTriggersPerfettoWhenAboveThreshold() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "true"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1"); + DeviceConfig.setProperties( + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + 1 /* traceThreshold */)); + + mLatencyTracker.onActionStart(action); + // We need to sleep here to ensure that the end call is past the set trace threshold (1ms) + Thread.sleep(5 /* millis */); + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).hasSize(1); + assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames().get(0)).isEqualTo( + "com.android.telemetry.latency-tracker-ACTION_SHOW_VOICE_INTERACTION"); + } + + @Test + public void testNeverTriggersPerfettoWhenBelowThreshold() + throws Exception { + // using a single test action, but this applies to all actions + int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION; + Map<String, String> deviceConfigProperties = new HashMap<>(); + deviceConfigProperties.put("action_show_voice_interaction_enable", "true"); + deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1"); + deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1000"); + DeviceConfig.setProperties( + new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER, + deviceConfigProperties)); + mLatencyTracker.waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + 1000 /* traceThreshold */)); + + mLatencyTracker.onActionStart(action); + // No sleep here to ensure that end call comes before 1000ms threshold + mLatencyTracker.onActionEnd(action); + assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty(); } - private List<Field> getAllActions() { - return Arrays.stream(LatencyTracker.class.getDeclaredFields()) - .filter(field -> field.getName().startsWith("ACTION_") - && Modifier.isStatic(field.getModifiers()) - && field.getType() == int.class) - .collect(Collectors.toList()); + private List<Field> getAllActionFields() { + return Arrays.stream(LatencyTracker.class.getDeclaredFields()).filter( + field -> field.getName().startsWith("ACTION_") && Modifier.isStatic( + field.getModifiers()) && field.getType() == int.class).collect( + Collectors.toList()); } private int getIntFieldChecked(Field field) { diff --git a/core/tests/coretests/src/com/android/internal/util/OWNERS b/core/tests/coretests/src/com/android/internal/util/OWNERS index d83274560315..dda11fb9a576 100644 --- a/core/tests/coretests/src/com/android/internal/util/OWNERS +++ b/core/tests/coretests/src/com/android/internal/util/OWNERS @@ -1,2 +1,3 @@ per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS -per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
\ No newline at end of file +per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS +per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS diff --git a/core/tests/coretests/testdoubles/Android.bp b/core/tests/coretests/testdoubles/Android.bp new file mode 100644 index 000000000000..35f6911ce481 --- /dev/null +++ b/core/tests/coretests/testdoubles/Android.bp @@ -0,0 +1,19 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + // SPDX-license-identifier-BSD + // legacy_unencumbered + default_applicable_licenses: ["frameworks_base_license"], +} + +filegroup { + name: "FrameworksCoreTestDoubles-sources", + srcs: ["src/**/*.java"], + visibility: [ + "//frameworks/base/core/tests/coretests", + "//frameworks/base/services/tests/voiceinteractiontests", + ], +} diff --git a/core/tests/coretests/testdoubles/OWNERS b/core/tests/coretests/testdoubles/OWNERS new file mode 100644 index 000000000000..baf92ec067c3 --- /dev/null +++ b/core/tests/coretests/testdoubles/OWNERS @@ -0,0 +1 @@ +per-file *LatencyTracker* = file:/core/java/com/android/internal/util/LATENCY_TRACKER_OWNERS diff --git a/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java new file mode 100644 index 000000000000..306ecdee9167 --- /dev/null +++ b/core/tests/coretests/testdoubles/src/com/android/internal/util/FakeLatencyTracker.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.util; + +import static com.android.internal.util.LatencyTracker.ActionProperties.ENABLE_SUFFIX; +import static com.android.internal.util.LatencyTracker.ActionProperties.SAMPLE_INTERVAL_SUFFIX; +import static com.android.internal.util.LatencyTracker.ActionProperties.TRACE_THRESHOLD_SUFFIX; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.ConditionVariable; +import android.provider.DeviceConfig; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.Nullable; + +import com.android.internal.annotations.GuardedBy; + +import com.google.common.collect.ImmutableMap; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; + +public final class FakeLatencyTracker extends LatencyTracker { + + private static final String TAG = "FakeLatencyTracker"; + private static final Duration FORCE_UPDATE_TIMEOUT = Duration.ofSeconds(1); + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private final Map<Integer, List<FrameworkStatsLogEvent>> mLatenciesLogged; + @GuardedBy("mLock") + private final List<String> mPerfettoTraceNamesTriggered; + private final AtomicReference<SparseArray<ActionProperties>> mLastPropertiesUpdate = + new AtomicReference<>(); + @Nullable + @GuardedBy("mLock") + private Callable<Boolean> mShouldClosePropertiesUpdatedCallable = null; + private final ConditionVariable mDeviceConfigPropertiesUpdated = new ConditionVariable(); + + public static FakeLatencyTracker create() throws Exception { + Log.i(TAG, "create"); + disableForAllActions(); + FakeLatencyTracker fakeLatencyTracker = new FakeLatencyTracker(); + // always return the fake in the disabled state and let the client control the desired state + fakeLatencyTracker.waitForGlobalEnabledState(false); + fakeLatencyTracker.waitForAllPropertiesEnableState(false); + return fakeLatencyTracker; + } + + FakeLatencyTracker() { + super(); + mLatenciesLogged = new HashMap<>(); + mPerfettoTraceNamesTriggered = new ArrayList<>(); + } + + private static void disableForAllActions() throws DeviceConfig.BadConfigException { + Map<String, String> properties = new HashMap<>(); + properties.put(LatencyTracker.SETTINGS_ENABLED_KEY, "false"); + for (int action : STATSD_ACTION) { + Log.d(TAG, "disabling action=" + action + ", property=" + getNameOfAction( + action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX); + properties.put(getNameOfAction(action).toLowerCase(Locale.ROOT) + ENABLE_SUFFIX, + "false"); + } + + DeviceConfig.setProperties( + new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties)); + } + + public void forceEnabled(int action, int traceThresholdMillis) + throws Exception { + String actionName = getNameOfAction(STATSD_ACTION[action]).toLowerCase(Locale.ROOT); + String actionEnableProperty = actionName + ENABLE_SUFFIX; + String actionSampleProperty = actionName + SAMPLE_INTERVAL_SUFFIX; + String actionTraceProperty = actionName + TRACE_THRESHOLD_SUFFIX; + Log.i(TAG, "setting property=" + actionTraceProperty + ", value=" + traceThresholdMillis); + Log.i(TAG, "setting property=" + actionEnableProperty + ", value=true"); + + Map<String, String> properties = new HashMap<>(ImmutableMap.of( + actionEnableProperty, "true", + // Fake forces to sample every event + actionSampleProperty, String.valueOf(1), + actionTraceProperty, String.valueOf(traceThresholdMillis) + )); + DeviceConfig.setProperties( + new DeviceConfig.Properties(DeviceConfig.NAMESPACE_LATENCY_TRACKER, properties)); + waitForMatchingActionProperties( + new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */, + traceThresholdMillis)); + } + + public List<FrameworkStatsLogEvent> getEventsWrittenToFrameworkStats(@Action int action) { + synchronized (mLock) { + Log.i(TAG, "getEventsWrittenToFrameworkStats: mLatenciesLogged=" + mLatenciesLogged); + return mLatenciesLogged.getOrDefault(action, Collections.emptyList()); + } + } + + public List<String> getTriggeredPerfettoTraceNames() { + synchronized (mLock) { + return mPerfettoTraceNamesTriggered; + } + } + + public void clearEvents() { + synchronized (mLock) { + mLatenciesLogged.clear(); + mPerfettoTraceNamesTriggered.clear(); + } + } + + @Override + public void onDeviceConfigPropertiesUpdated(SparseArray<ActionProperties> actionProperties) { + Log.d(TAG, "onDeviceConfigPropertiesUpdated: " + actionProperties); + mLastPropertiesUpdate.set(actionProperties); + synchronized (mLock) { + if (mShouldClosePropertiesUpdatedCallable != null) { + try { + boolean shouldClosePropertiesUpdated = + mShouldClosePropertiesUpdatedCallable.call(); + Log.i(TAG, "shouldClosePropertiesUpdatedCallable callable result=" + + shouldClosePropertiesUpdated); + if (shouldClosePropertiesUpdated) { + Log.i(TAG, "shouldClosePropertiesUpdatedCallable=true, opening condition"); + mShouldClosePropertiesUpdatedCallable = null; + mDeviceConfigPropertiesUpdated.open(); + } + } catch (Exception e) { + Log.e(TAG, "exception when calling callable", e); + throw new RuntimeException(e); + } + } else { + Log.i(TAG, "no conditional callable set, opening condition"); + mDeviceConfigPropertiesUpdated.open(); + } + } + } + + @Override + public void onTriggerPerfetto(String triggerName) { + synchronized (mLock) { + mPerfettoTraceNamesTriggered.add(triggerName); + } + } + + @Override + public void onLogToFrameworkStats(FrameworkStatsLogEvent event) { + synchronized (mLock) { + Log.i(TAG, "onLogToFrameworkStats: event=" + event); + List<FrameworkStatsLogEvent> eventList = mLatenciesLogged.getOrDefault(event.action, + new ArrayList<>()); + eventList.add(event); + mLatenciesLogged.put(event.action, eventList); + } + } + + public void waitForAllPropertiesEnableState(boolean enabledState) throws Exception { + Log.i(TAG, "waitForAllPropertiesEnableState: enabledState=" + enabledState); + synchronized (mLock) { + Log.i(TAG, "closing condition"); + mDeviceConfigPropertiesUpdated.close(); + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + mShouldClosePropertiesUpdatedCallable = () -> { + Log.i(TAG, "verifying if last properties update has all properties enable=" + + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + for (int i = 0; i < newProperties.size(); i++) { + if (newProperties.get(i).isEnabled() != enabledState) { + return false; + } + } + } + return true; + }; + if (mShouldClosePropertiesUpdatedCallable.call()) { + return; + } + } + Log.i(TAG, "waiting for condition"); + assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue(); + } + + public void waitForMatchingActionProperties(ActionProperties actionProperties) + throws Exception { + Log.i(TAG, "waitForMatchingActionProperties: actionProperties=" + actionProperties); + synchronized (mLock) { + Log.i(TAG, "closing condition"); + mDeviceConfigPropertiesUpdated.close(); + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + mShouldClosePropertiesUpdatedCallable = () -> { + Log.i(TAG, "verifying if last properties update contains matching property =" + + actionProperties); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(actionProperties.getAction()).equals( + actionProperties); + } + } + return false; + }; + if (mShouldClosePropertiesUpdatedCallable.call()) { + return; + } + } + Log.i(TAG, "waiting for condition"); + assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue(); + } + + public void waitForActionEnabledState(int action, boolean enabledState) throws Exception { + Log.i(TAG, "waitForActionEnabledState:" + + " action=" + action + ", enabledState=" + enabledState); + synchronized (mLock) { + Log.i(TAG, "closing condition"); + mDeviceConfigPropertiesUpdated.close(); + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + mShouldClosePropertiesUpdatedCallable = () -> { + Log.i(TAG, "verifying if last properties update contains action=" + action + + ", enabledState=" + enabledState); + SparseArray<ActionProperties> newProperties = mLastPropertiesUpdate.get(); + if (newProperties != null) { + if (newProperties.size() > 0) { + return newProperties.get(action).isEnabled() == enabledState; + } + } + return false; + }; + if (mShouldClosePropertiesUpdatedCallable.call()) { + return; + } + } + Log.i(TAG, "waiting for condition"); + assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue(); + } + + public void waitForGlobalEnabledState(boolean enabledState) throws Exception { + Log.i(TAG, "waitForGlobalEnabledState: enabledState=" + enabledState); + synchronized (mLock) { + Log.i(TAG, "closing condition"); + mDeviceConfigPropertiesUpdated.close(); + // Update the callable to only close the properties updated condition when all the + // desired properties have been updated. The DeviceConfig callbacks may happen multiple + // times so testing the resulting updates is required. + mShouldClosePropertiesUpdatedCallable = () -> { + //noinspection deprecation + return isEnabled() == enabledState; + }; + if (mShouldClosePropertiesUpdatedCallable.call()) { + return; + } + } + Log.i(TAG, "waiting for condition"); + assertThat(mDeviceConfigPropertiesUpdated.block(FORCE_UPDATE_TIMEOUT.toMillis())).isTrue(); + } +} diff --git a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java index 6fa1a694eb67..372e4cb3d72e 100644 --- a/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java +++ b/keystore/java/android/security/keystore2/KeyStoreCryptoOperationUtils.java @@ -40,7 +40,6 @@ import java.security.InvalidKeyException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import java.util.Random; /** * Assorted utility methods for implementing crypto operations on top of KeyStore. @@ -50,7 +49,6 @@ import java.util.Random; abstract class KeyStoreCryptoOperationUtils { private static volatile SecureRandom sRng; - private static final Random sRandom = new Random(); private KeyStoreCryptoOperationUtils() {} @@ -213,7 +211,7 @@ abstract class KeyStoreCryptoOperationUtils { } else { // Keystore won't give us an operation challenge if the operation doesn't // need user authorization. So we make our own. - return sRandom.nextLong(); + return getRng().nextLong(); } } } diff --git a/libs/WindowManager/Shell/res/color/unfold_background.xml b/libs/WindowManager/Shell/res/color/unfold_background.xml new file mode 100644 index 000000000000..e33eb126012d --- /dev/null +++ b/libs/WindowManager/Shell/res/color/unfold_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="5" /> +</selector>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml index 413cfd78fd91..a993469aaccf 100644 --- a/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml +++ b/libs/WindowManager/Shell/res/layout/letterbox_education_dialog_layout.xml @@ -1,5 +1,5 @@ <!-- - ~ Copyright (C) 2022 The Android Open Source Project + ~ Copyright (C) 2023 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout +<com.android.wm.shell.compatui.LetterboxEduDialogLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" style="@style/LetterboxDialog"> @@ -78,13 +78,13 @@ android:orientation="horizontal" android:paddingTop="48dp"> - <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout + <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:icon="@drawable/letterbox_education_ic_reposition" app:text="@string/letterbox_education_reposition_text"/> - <com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogActionLayout + <com.android.wm.shell.compatui.LetterboxEduDialogActionLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart= @@ -118,4 +118,4 @@ </FrameLayout> -</com.android.wm.shell.compatui.letterboxedu.LetterboxEduDialogLayout> +</com.android.wm.shell.compatui.LetterboxEduDialogLayout> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index e24c2286013d..85a353f2d586 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -206,12 +206,14 @@ public class Bubble implements BubbleViewProvider { public Bubble(Intent intent, UserHandle user, + @Nullable Icon icon, Executor mainExecutor) { mKey = KEY_APP_BUBBLE; mGroupKey = null; mLocusId = null; mFlags = 0; mUser = user; + mIcon = icon; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; mTaskId = INVALID_TASK_ID; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 48fe65d3ce59..d2889e782aea 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -57,6 +57,7 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.drawable.Icon; import android.os.Binder; import android.os.Handler; import android.os.RemoteException; @@ -1034,8 +1035,9 @@ public class BubbleController implements ConfigurationChangeListener { * * @param intent the intent to display in the bubble expanded view. * @param user the {@link UserHandle} of the user to start this activity for. + * @param icon the {@link Icon} to use for the bubble view. */ - public void showOrHideAppBubble(Intent intent, UserHandle user) { + public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) { if (intent == null || intent.getPackage() == null) { Log.w(TAG, "App bubble failed to show, invalid intent: " + intent + ((intent != null) ? " with package: " + intent.getPackage() : " ")); @@ -1063,7 +1065,7 @@ public class BubbleController implements ConfigurationChangeListener { } } else { // App bubble does not exist, lets add and expand it - Bubble b = new Bubble(intent, user, mMainExecutor); + Bubble b = new Bubble(intent, user, icon, mMainExecutor); b.setShouldAutoExpand(true); inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); } @@ -1871,9 +1873,9 @@ public class BubbleController implements ConfigurationChangeListener { } @Override - public void showOrHideAppBubble(Intent intent, UserHandle user) { + public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) { mMainExecutor.execute( - () -> BubbleController.this.showOrHideAppBubble(intent, user)); + () -> BubbleController.this.showOrHideAppBubble(intent, user, icon)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 5555bec6a28e..876a720f7722 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -26,6 +26,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.Intent; import android.content.pm.UserInfo; +import android.graphics.drawable.Icon; import android.hardware.HardwareBuffer; import android.os.UserHandle; import android.service.notification.NotificationListenerService; @@ -135,8 +136,9 @@ public interface Bubbles { * * @param intent the intent to display in the bubble expanded view. * @param user the {@link UserHandle} of the user to start this activity for. + * @param icon the {@link Icon} to use for the bubble view. */ - void showOrHideAppBubble(Intent intent, UserHandle user); + void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon); /** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */ boolean isAppBubbleTaskId(int taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index 76d9152fdfbc..6950f24512b1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -42,7 +42,6 @@ import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState; -import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java index fe95d04bad3c..170c0ee91b40 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java @@ -38,7 +38,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.compatui.CompatUIController.CompatUICallback; -import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import java.util.function.Consumer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java index 02197f644a39..9974295123b7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogActionLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogActionLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import android.content.Context; import android.content.res.TypedArray; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java index 9232f36cf939..df2f6ce24ebc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduDialogLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import android.annotation.Nullable; import android.content.Context; @@ -26,7 +26,6 @@ import android.widget.TextView; import androidx.constraintlayout.widget.ConstraintLayout; import com.android.wm.shell.R; -import com.android.wm.shell.compatui.DialogContainerSupplier; /** * Container for Letterbox Education Dialog and background dim. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java index c14c009721a1..bfdbfe3d6ea0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/LetterboxEduWindowManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; @@ -36,8 +36,6 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract; -import com.android.wm.shell.compatui.DialogAnimationController; import com.android.wm.shell.transition.Transitions; /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 57b5b8f24fad..3d5230d5cf90 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -82,7 +82,6 @@ import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingSurface; @@ -521,9 +520,6 @@ public abstract class WMShellBaseModule { desktopModeTaskRepository, mainExecutor)); } - @BindsOptionalOf - abstract RecentsTransitionHandler optionalRecentsTransitionHandler(); - // // Shell transitions // @@ -807,7 +803,6 @@ public abstract class WMShellBaseModule { Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformComponents> freeformComponents, Optional<RecentTasksController> recentTasksOptional, - Optional<RecentsTransitionHandler> recentsTransitionHandlerOptional, Optional<OneHandedController> oneHandedControllerOptional, Optional<HideDisplayCutoutController> hideDisplayCutoutControllerOptional, Optional<ActivityEmbeddingController> activityEmbeddingOptional, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index cc0da2840fa0..7a83d101578f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -83,7 +83,6 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipSizeSpecHandler; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -529,20 +528,9 @@ public abstract class WMShellModule { ShellInit shellInit, Optional<SplitScreenController> splitScreenOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, - Optional<RecentsTransitionHandler> recentsTransitionHandler, Transitions transitions) { return new DefaultMixedHandler(shellInit, transitions, splitScreenOptional, - pipTouchHandlerOptional, recentsTransitionHandler); - } - - @WMSingleton - @Provides - static RecentsTransitionHandler provideRecentsTransitionHandler( - ShellInit shellInit, - Transitions transitions, - Optional<RecentTasksController> recentTasksController) { - return new RecentsTransitionHandler(shellInit, transitions, - recentTasksController.orElse(null)); + pipTouchHandlerOptional); } // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 52f5a8cfd8e0..c19d54365309 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; @@ -536,6 +537,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds); return; } + if (mSplitScreenOptional.isPresent()) { + // If pip activity will reparent to origin task case and if the origin task still under + // split root, just exit split screen here to ensure it could expand to fullscreen. + SplitScreenController split = mSplitScreenOptional.get(); + if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) { + split.exitSplitScreen(INVALID_TASK_ID, + SplitScreenController.EXIT_REASON_APP_FINISHED); + } + } mSyncTransactionQueue.queue(wct); mSyncTransactionQueue.runInSync(t -> { // Make sure to grab the latest source hint rect as it could have been @@ -1479,9 +1489,13 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, applyFinishBoundsResize(wct, direction, false); } } else { - final boolean isPipTopLeft = - direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && isPipToTopLeft(); - applyFinishBoundsResize(wct, direction, isPipTopLeft); + applyFinishBoundsResize(wct, direction, isPipToTopLeft()); + // Use sync transaction to apply finish transaction for enter split case. + if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + mSyncTransactionQueue.runInSync(t -> { + t.merge(tx); + }); + } } finishResizeForMenu(destinationBounds); @@ -1518,7 +1532,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); wct.setBounds(mToken, taskBounds); - wct.setBoundsChangeTransaction(mToken, tx); + // Pip to split should use sync transaction to sync split bounds change. + if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) { + wct.setBoundsChangeTransaction(mToken, tx); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 979b7c7dc31f..167c0321d3ad 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -282,7 +282,8 @@ public class PipMenuView extends FrameLayout { public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent() - && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); + && mSplitScreenControllerOptional.get().isTaskInSplitScreenForeground( + taskInfo.taskId); mFocusedTaskAllowSplitScreen = isSplitScreen || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && taskInfo.supportsMultiWindow diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl index 4048c5b8feab..1a6c1d65db03 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl @@ -17,11 +17,6 @@ package com.android.wm.shell.recents; import android.app.ActivityManager.RunningTaskInfo; -import android.app.IApplicationThread; -import android.app.PendingIntent; -import android.content.Intent; -import android.os.Bundle; -import android.view.IRecentsAnimationRunner; import com.android.wm.shell.recents.IRecentTasksListener; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -50,10 +45,4 @@ interface IRecentTasks { * Gets the set of running tasks. */ RunningTaskInfo[] getRunningTasks(int maxNum) = 4; - - /** - * Starts a recents transition. - */ - oneway void startRecentsTransition(in PendingIntent intent, in Intent fillIn, in Bundle options, - IApplicationThread appThread, IRecentsAnimationRunner listener) = 5; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index c5bfd8753994..0d9faa3c6f83 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -24,18 +24,13 @@ import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RE import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.app.IApplicationThread; -import android.app.PendingIntent; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; -import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; -import android.view.IRecentsAnimationRunner; import androidx.annotation.BinderThread; import androidx.annotation.NonNull; @@ -84,7 +79,6 @@ public class RecentTasksController implements TaskStackListenerCallback, private final TaskStackListenerImpl mTaskStackListener; private final RecentTasksImpl mImpl = new RecentTasksImpl(); private final ActivityTaskManager mActivityTaskManager; - private RecentsTransitionHandler mTransitionHandler = null; private IRecentTasksListener mListener; private final boolean mIsDesktopMode; @@ -156,10 +150,6 @@ public class RecentTasksController implements TaskStackListenerCallback, mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTaskListener(this)); } - void setTransitionHandler(RecentsTransitionHandler handler) { - mTransitionHandler = handler; - } - /** * Adds a split pair. This call does not validate the taskIds, only that they are not the same. */ @@ -502,18 +492,5 @@ public class RecentTasksController implements TaskStackListenerCallback, true /* blocking */); return tasks[0]; } - - @Override - public void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, - IApplicationThread appThread, IRecentsAnimationRunner listener) { - if (mController.mTransitionHandler == null) { - Slog.e(TAG, "Used shell-transitions startRecentsTransition without" - + " shell-transitions"); - return; - } - executeRemoteCallWithTaskPermission(mController, "startRecentsTransition", - (controller) -> controller.mTransitionHandler.startRecentsTransition( - intent, fillIn, options, appThread, listener)); - } } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java deleted file mode 100644 index da8c805eb038..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ /dev/null @@ -1,759 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.recents; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; -import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; -import static android.view.WindowManager.TRANSIT_SLEEP; -import static android.view.WindowManager.TRANSIT_TO_FRONT; - -import android.annotation.Nullable; -import android.annotation.SuppressLint; -import android.app.ActivityManager; -import android.app.ActivityTaskManager; -import android.app.IApplicationThread; -import android.app.PendingIntent; -import android.content.Intent; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.ArrayMap; -import android.util.Slog; -import android.view.IRecentsAnimationController; -import android.view.IRecentsAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.window.PictureInPictureSurfaceTransaction; -import android.window.TaskSnapshot; -import android.window.TransitionInfo; -import android.window.TransitionRequestInfo; -import android.window.WindowContainerToken; -import android.window.WindowContainerTransaction; - -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.transition.Transitions; -import com.android.wm.shell.util.TransitionUtil; - -import java.util.ArrayList; - -/** - * Handles the Recents (overview) animation. Only one of these can run at a time. A recents - * transition must be created via {@link #startRecentsTransition}. Anything else will be ignored. - */ -public class RecentsTransitionHandler implements Transitions.TransitionHandler { - private static final String TAG = "RecentsTransitionHandler"; - - private final Transitions mTransitions; - private final ShellExecutor mExecutor; - private IApplicationThread mAnimApp = null; - private final ArrayList<RecentsController> mControllers = new ArrayList<>(); - - /** - * List of other handlers which might need to mix recents with other things. These are checked - * in the order they are added. Ideally there should only be one. - */ - private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); - - public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, - @Nullable RecentTasksController recentTasksController) { - mTransitions = transitions; - mExecutor = transitions.getMainExecutor(); - if (!Transitions.ENABLE_SHELL_TRANSITIONS) return; - if (recentTasksController == null) return; - shellInit.addInitCallback(() -> { - recentTasksController.setTransitionHandler(this); - transitions.addHandler(this); - }, this); - } - - /** Register a mixer handler. {@see RecentsMixedHandler}*/ - public void addMixer(RecentsMixedHandler mixer) { - mMixers.add(mixer); - } - - /** Unregister a Mixed Handler */ - public void removeMixer(RecentsMixedHandler mixer) { - mMixers.remove(mixer); - } - - void startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, - IApplicationThread appThread, IRecentsAnimationRunner listener) { - // only care about latest one. - mAnimApp = appThread; - WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.sendPendingIntent(intent, fillIn, options); - final RecentsController controller = new RecentsController(listener); - RecentsMixedHandler mixer = null; - Transitions.TransitionHandler mixedHandler = null; - for (int i = 0; i < mMixers.size(); ++i) { - mixedHandler = mMixers.get(i).handleRecentsRequest(wct); - if (mixedHandler != null) { - mixer = mMixers.get(i); - break; - } - } - final IBinder transition = mTransitions.startTransition(TRANSIT_TO_FRONT, wct, - mixedHandler == null ? this : mixedHandler); - if (mixer != null) { - mixer.setRecentsTransition(transition); - } - if (transition == null) { - controller.cancel(); - return; - } - controller.setTransition(transition); - mControllers.add(controller); - } - - @Override - public WindowContainerTransaction handleRequest(IBinder transition, - TransitionRequestInfo request) { - // do not directly handle requests. Only entry point should be via startRecentsTransition - return null; - } - - private int findController(IBinder transition) { - for (int i = mControllers.size() - 1; i >= 0; --i) { - if (mControllers.get(i).mTransition == transition) return i; - } - return -1; - } - - @Override - public boolean startAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction startTransaction, - SurfaceControl.Transaction finishTransaction, - Transitions.TransitionFinishCallback finishCallback) { - final int controllerIdx = findController(transition); - if (controllerIdx < 0) return false; - final RecentsController controller = mControllers.get(controllerIdx); - Transitions.setRunningRemoteTransitionDelegate(mAnimApp); - mAnimApp = null; - if (!controller.start(info, startTransaction, finishTransaction, finishCallback)) { - return false; - } - return true; - } - - @Override - public void mergeAnimation(IBinder transition, TransitionInfo info, - SurfaceControl.Transaction t, IBinder mergeTarget, - Transitions.TransitionFinishCallback finishCallback) { - final int targetIdx = findController(mergeTarget); - if (targetIdx < 0) return; - final RecentsController controller = mControllers.get(targetIdx); - controller.merge(info, t, finishCallback); - } - - @Override - public void onTransitionConsumed(IBinder transition, boolean aborted, - SurfaceControl.Transaction finishTransaction) { - final int idx = findController(transition); - if (idx < 0) return; - mControllers.get(idx).cancel(); - } - - /** There is only one of these and it gets reset on finish. */ - private class RecentsController extends IRecentsAnimationController.Stub { - private IRecentsAnimationRunner mListener; - private IBinder.DeathRecipient mDeathHandler; - private Transitions.TransitionFinishCallback mFinishCB = null; - private SurfaceControl.Transaction mFinishTransaction = null; - - /** - * List of tasks that we are switching away from via this transition. Upon finish, these - * pausing tasks will become invisible. - * These need to be ordered since the order must be restored if there is no task-switch. - */ - private ArrayList<TaskState> mPausingTasks = null; - - /** - * List of tasks that we are switching to. Upon finish, these will remain visible and - * on top. - */ - private ArrayList<TaskState> mOpeningTasks = null; - - private WindowContainerToken mPipTask = null; - private WindowContainerToken mRecentsTask = null; - private int mRecentsTaskId = -1; - private TransitionInfo mInfo = null; - private boolean mOpeningSeparateHome = false; - private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null; - private PictureInPictureSurfaceTransaction mPipTransaction = null; - private IBinder mTransition = null; - private boolean mKeyguardLocked = false; - private boolean mWillFinishToHome = false; - - /** The animation is idle, waiting for the user to choose a task to switch to. */ - private static final int STATE_NORMAL = 0; - - /** The user chose a new task to switch to and the animation is animating to it. */ - private static final int STATE_NEW_TASK = 1; - - /** The latest state that the recents animation is operating in. */ - private int mState = STATE_NORMAL; - - RecentsController(IRecentsAnimationRunner listener) { - mListener = listener; - mDeathHandler = () -> mExecutor.execute(() -> { - if (mListener == null) return; - if (mFinishCB != null) { - finish(mWillFinishToHome, false /* leaveHint */); - } - }); - try { - mListener.asBinder().linkToDeath(mDeathHandler, 0 /* flags */); - } catch (RemoteException e) { - mListener = null; - } - } - - void setTransition(IBinder transition) { - mTransition = transition; - } - - void cancel() { - // restoring (to-home = false) involves submitting more WM changes, so by default, use - // toHome = true when canceling. - cancel(true /* toHome */); - } - - void cancel(boolean toHome) { - if (mFinishCB != null && mListener != null) { - try { - mListener.onAnimationCanceled(null, null); - } catch (RemoteException e) { - Slog.e(TAG, "Error canceling recents animation", e); - } - finish(toHome, false /* userLeave */); - } else { - cleanUp(); - } - } - - /** - * Sends a cancel message to the recents animation with snapshots. Used to trigger a - * "replace-with-screenshot" like behavior. - */ - private boolean sendCancelWithSnapshots() { - int[] taskIds = null; - TaskSnapshot[] snapshots = null; - if (mPausingTasks.size() > 0) { - taskIds = new int[mPausingTasks.size()]; - snapshots = new TaskSnapshot[mPausingTasks.size()]; - try { - for (int i = 0; i < mPausingTasks.size(); ++i) { - snapshots[i] = ActivityTaskManager.getService().takeTaskSnapshot( - mPausingTasks.get(0).mTaskInfo.taskId); - } - } catch (RemoteException e) { - taskIds = null; - snapshots = null; - } - } - try { - mListener.onAnimationCanceled(taskIds, snapshots); - } catch (RemoteException e) { - Slog.e(TAG, "Error canceling recents animation", e); - return false; - } - return true; - } - - void cleanUp() { - if (mListener != null && mDeathHandler != null) { - mListener.asBinder().unlinkToDeath(mDeathHandler, 0 /* flags */); - mDeathHandler = null; - } - mListener = null; - mFinishCB = null; - // clean-up leash surfacecontrols and anything that might reference them. - if (mLeashMap != null) { - for (int i = 0; i < mLeashMap.size(); ++i) { - mLeashMap.valueAt(i).release(); - } - mLeashMap = null; - } - mFinishTransaction = null; - mPausingTasks = null; - mOpeningTasks = null; - mInfo = null; - mTransition = null; - mControllers.remove(this); - } - - boolean start(TransitionInfo info, SurfaceControl.Transaction t, - SurfaceControl.Transaction finishT, Transitions.TransitionFinishCallback finishCB) { - if (mListener == null || mTransition == null) { - cleanUp(); - return false; - } - // First see if this is a valid recents transition. - boolean hasPausingTasks = false; - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (TransitionUtil.isWallpaper(change)) continue; - if (TransitionUtil.isClosingType(change.getMode())) { - hasPausingTasks = true; - continue; - } - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { - mRecentsTask = taskInfo.token; - mRecentsTaskId = taskInfo.taskId; - } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { - mRecentsTask = taskInfo.token; - mRecentsTaskId = taskInfo.taskId; - } - } - if (mRecentsTask == null || !hasPausingTasks) { - // Recents is already running apparently, so this is a no-op. - Slog.e(TAG, "Tried to start recents while it is already running. recents=" - + mRecentsTask); - cleanUp(); - return false; - } - - mInfo = info; - mFinishCB = finishCB; - mFinishTransaction = finishT; - mPausingTasks = new ArrayList<>(); - mOpeningTasks = new ArrayList<>(); - mLeashMap = new ArrayMap<>(); - mKeyguardLocked = (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0; - - final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>(); - final ArrayList<RemoteAnimationTarget> wallpapers = new ArrayList<>(); - TransitionUtil.LeafTaskFilter leafTaskFilter = new TransitionUtil.LeafTaskFilter(); - // About layering: we divide up the "layer space" into 3 regions (each the size of - // the change count). This lets us categorize things into above/below/between - // while maintaining their relative ordering. - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (TransitionUtil.isWallpaper(change)) { - final RemoteAnimationTarget target = TransitionUtil.newTarget(change, - // wallpapers go into the "below" layer space - info.getChanges().size() - i, info, t, mLeashMap); - wallpapers.add(target); - // Make all the wallpapers opaque since we want them visible from the start - t.setAlpha(target.leash, 1); - } else if (leafTaskFilter.test(change)) { - // start by putting everything into the "below" layer space. - final RemoteAnimationTarget target = TransitionUtil.newTarget(change, - info.getChanges().size() - i, info, t, mLeashMap); - apps.add(target); - if (TransitionUtil.isClosingType(change.getMode())) { - // raise closing (pausing) task to "above" layer so it isn't covered - t.setLayer(target.leash, info.getChanges().size() * 3 - i); - mPausingTasks.add(new TaskState(change, target.leash)); - if (taskInfo.pictureInPictureParams != null - && taskInfo.pictureInPictureParams.isAutoEnterEnabled()) { - mPipTask = taskInfo.token; - } - } else if (taskInfo != null - && taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) { - // There's a 3p launcher, so make sure recents goes above that. - t.setLayer(target.leash, info.getChanges().size() * 3 - i); - } else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { - // do nothing - } else if (TransitionUtil.isOpeningType(change.getMode())) { - mOpeningTasks.add(new TaskState(change, target.leash)); - } - } - } - t.apply(); - try { - mListener.onAnimationStart(this, - apps.toArray(new RemoteAnimationTarget[apps.size()]), - wallpapers.toArray(new RemoteAnimationTarget[wallpapers.size()]), - new Rect(0, 0, 0, 0), new Rect()); - } catch (RemoteException e) { - Slog.e(TAG, "Error starting recents animation", e); - cancel(); - } - return true; - } - - @SuppressLint("NewApi") - void merge(TransitionInfo info, SurfaceControl.Transaction t, - Transitions.TransitionFinishCallback finishCallback) { - if (mFinishCB == null) { - // This was no-op'd (likely a repeated start) and we've already sent finish. - return; - } - if (info.getType() == TRANSIT_SLEEP) { - // A sleep event means we need to stop animations immediately, so cancel here. - cancel(); - return; - } - ArrayList<TransitionInfo.Change> openingTasks = null; - ArrayList<TransitionInfo.Change> closingTasks = null; - mOpeningSeparateHome = false; - TransitionInfo.Change recentsOpening = null; - boolean foundRecentsClosing = false; - boolean hasChangingApp = false; - final TransitionUtil.LeafTaskFilter leafTaskFilter = - new TransitionUtil.LeafTaskFilter(); - for (int i = 0; i < info.getChanges().size(); ++i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - final boolean isLeafTask = leafTaskFilter.test(change); - if (TransitionUtil.isOpeningType(change.getMode())) { - if (mRecentsTask.equals(change.getContainer())) { - recentsOpening = change; - } else if (isLeafTask) { - if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) { - // This is usually a 3p launcher - mOpeningSeparateHome = true; - } - if (openingTasks == null) { - openingTasks = new ArrayList<>(); - } - openingTasks.add(change); - } - } else if (TransitionUtil.isClosingType(change.getMode())) { - if (mRecentsTask.equals(change.getContainer())) { - foundRecentsClosing = true; - } else if (isLeafTask) { - if (closingTasks == null) { - closingTasks = new ArrayList<>(); - } - closingTasks.add(change); - } - } else if (change.getMode() == TRANSIT_CHANGE) { - // Finish recents animation if the display is changed, so the default - // transition handler can play the animation such as rotation effect. - if (change.hasFlags(TransitionInfo.FLAG_IS_DISPLAY)) { - cancel(mWillFinishToHome); - return; - } - hasChangingApp = true; - } - } - if (hasChangingApp && foundRecentsClosing) { - // This happens when a visible app is expanding (usually PiP). In this case, - // that transition probably has a special-purpose animation, so finish recents - // now and let it do its animation (since recents is going to be occluded). - sendCancelWithSnapshots(); - mExecutor.executeDelayed( - () -> finishInner(true /* toHome */, false /* userLeaveHint */), 0); - return; - } - if (recentsOpening != null) { - // the recents task re-appeared. This happens if the user gestures before the - // task-switch (NEW_TASK) animation finishes. - if (mState == STATE_NORMAL) { - Slog.e(TAG, "Returning to recents while recents is already idle."); - } - if (closingTasks == null || closingTasks.size() == 0) { - Slog.e(TAG, "Returning to recents without closing any opening tasks."); - } - // Setup may hide it initially since it doesn't know that overview was still active. - t.show(recentsOpening.getLeash()); - t.setAlpha(recentsOpening.getLeash(), 1.f); - mState = STATE_NORMAL; - } - boolean didMergeThings = false; - if (closingTasks != null) { - // Cancelling a task-switch. Move the tasks back to mPausing from mOpening - for (int i = 0; i < closingTasks.size(); ++i) { - final TransitionInfo.Change change = closingTasks.get(i); - int openingIdx = TaskState.indexOf(mOpeningTasks, change); - if (openingIdx < 0) { - Slog.e(TAG, "Back to existing recents animation from an unrecognized " - + "task: " + change.getTaskInfo().taskId); - continue; - } - mPausingTasks.add(mOpeningTasks.remove(openingIdx)); - didMergeThings = true; - } - } - RemoteAnimationTarget[] appearedTargets = null; - if (openingTasks != null && openingTasks.size() > 0) { - // Switching to some new tasks, add to mOpening and remove from mPausing. Also, - // enter NEW_TASK state since this will start the switch-to animation. - final int layer = mInfo.getChanges().size() * 3; - appearedTargets = new RemoteAnimationTarget[openingTasks.size()]; - for (int i = 0; i < openingTasks.size(); ++i) { - final TransitionInfo.Change change = openingTasks.get(i); - int pausingIdx = TaskState.indexOf(mPausingTasks, change); - if (pausingIdx >= 0) { - // Something is showing/opening a previously-pausing app. - appearedTargets[i] = TransitionUtil.newTarget( - change, layer, mPausingTasks.get(pausingIdx).mLeash); - mOpeningTasks.add(mPausingTasks.remove(pausingIdx)); - // Setup hides opening tasks initially, so make it visible again (since we - // are already showing it). - t.show(change.getLeash()); - t.setAlpha(change.getLeash(), 1.f); - } else { - // We are receiving new opening tasks, so convert to onTasksAppeared. - appearedTargets[i] = TransitionUtil.newTarget( - change, layer, info, t, mLeashMap); - // reparent into the original `mInfo` since that's where we are animating. - final int rootIdx = TransitionUtil.rootIndexFor(change, mInfo); - t.reparent(appearedTargets[i].leash, mInfo.getRoot(rootIdx).getLeash()); - t.setLayer(appearedTargets[i].leash, layer); - mOpeningTasks.add(new TaskState(change, appearedTargets[i].leash)); - } - } - didMergeThings = true; - mState = STATE_NEW_TASK; - } - if (!didMergeThings) { - // Didn't recognize anything in incoming transition so don't merge it. - Slog.w(TAG, "Don't know how to merge this transition."); - return; - } - // At this point, we are accepting the merge. - t.apply(); - // not using the incoming anim-only surfaces - info.releaseAnimSurfaces(); - finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); - if (appearedTargets == null) return; - try { - mListener.onTasksAppeared(appearedTargets); - } catch (RemoteException e) { - Slog.e(TAG, "Error sending appeared tasks to recents animation", e); - } - } - - @Override - public TaskSnapshot screenshotTask(int taskId) { - try { - return ActivityTaskManager.getService().takeTaskSnapshot(taskId); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to screenshot task", e); - } - return null; - } - - @Override - public void setInputConsumerEnabled(boolean enabled) { - mExecutor.execute(() -> { - if (mFinishCB == null || !enabled) return; - // transient launches don't receive focus automatically. Since we are taking over - // the gesture now, take focus explicitly. - // This also moves recents back to top if the user gestured before a switch - // animation finished. - try { - ActivityTaskManager.getService().setFocusedTask(mRecentsTaskId); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to set focused task", e); - } - }); - } - - @Override - public void setAnimationTargetsBehindSystemBars(boolean behindSystemBars) { - } - - @Override - public void setFinishTaskTransaction(int taskId, - PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { - mExecutor.execute(() -> { - if (mFinishCB == null) return; - mPipTransaction = finishTransaction; - }); - } - - @Override - @SuppressLint("NewApi") - public void finish(boolean toHome, boolean sendUserLeaveHint) { - mExecutor.execute(() -> finishInner(toHome, sendUserLeaveHint)); - } - - private void finishInner(boolean toHome, boolean sendUserLeaveHint) { - if (mFinishCB == null) { - Slog.e(TAG, "Duplicate call to finish"); - return; - } - final Transitions.TransitionFinishCallback finishCB = mFinishCB; - mFinishCB = null; - - final SurfaceControl.Transaction t = mFinishTransaction; - final WindowContainerTransaction wct = new WindowContainerTransaction(); - - if (mKeyguardLocked && mRecentsTask != null) { - if (toHome) wct.reorder(mRecentsTask, true /* toTop */); - else wct.restoreTransientOrder(mRecentsTask); - } - if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) { - // The gesture is returning to the pausing-task(s) rather than continuing with - // recents, so end the transition by moving the app back to the top (and also - // re-showing it's task). - for (int i = mPausingTasks.size() - 1; i >= 0; --i) { - // reverse order so that index 0 ends up on top - wct.reorder(mPausingTasks.get(i).mToken, true /* onTop */); - t.show(mPausingTasks.get(i).mTaskSurface); - } - if (!mKeyguardLocked && mRecentsTask != null) { - wct.restoreTransientOrder(mRecentsTask); - } - } else if (toHome && mOpeningSeparateHome && mPausingTasks != null) { - // Special situation where 3p launcher was changed during recents (this happens - // during tapltests...). Here we get both "return to home" AND "home opening". - // This is basically going home, but we have to restore the recents and home order. - for (int i = 0; i < mOpeningTasks.size(); ++i) { - final TaskState state = mOpeningTasks.get(i); - if (state.mTaskInfo.topActivityType == ACTIVITY_TYPE_HOME) { - // Make sure it is on top. - wct.reorder(state.mToken, true /* onTop */); - } - t.show(state.mTaskSurface); - } - for (int i = mPausingTasks.size() - 1; i >= 0; --i) { - t.hide(mPausingTasks.get(i).mTaskSurface); - } - if (!mKeyguardLocked && mRecentsTask != null) { - wct.restoreTransientOrder(mRecentsTask); - } - } else { - // The general case: committing to recents, going home, or switching tasks. - for (int i = 0; i < mOpeningTasks.size(); ++i) { - t.show(mOpeningTasks.get(i).mTaskSurface); - } - for (int i = 0; i < mPausingTasks.size(); ++i) { - if (!sendUserLeaveHint) { - // This means recents is not *actually* finishing, so of course we gotta - // do special stuff in WMCore to accommodate. - wct.setDoNotPip(mPausingTasks.get(i).mToken); - } - // Since we will reparent out of the leashes, pre-emptively hide the child - // surface to match the leash. Otherwise, there will be a flicker before the - // visibility gets committed in Core when using split-screen (in splitscreen, - // the leaf-tasks are not "independent" so aren't hidden by normal setup). - t.hide(mPausingTasks.get(i).mTaskSurface); - } - if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) { - t.show(mInfo.getChange(mPipTask).getLeash()); - PictureInPictureSurfaceTransaction.apply(mPipTransaction, - mInfo.getChange(mPipTask).getLeash(), t); - mPipTask = null; - mPipTransaction = null; - } - } - cleanUp(); - finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */); - } - - @Override - public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) { - } - - @Override - public void cleanupScreenshot() { - } - - @Override - public void setWillFinishToHome(boolean willFinishToHome) { - mExecutor.execute(() -> { - mWillFinishToHome = willFinishToHome; - }); - } - - /** - * @see IRecentsAnimationController#removeTask - */ - @Override - public boolean removeTask(int taskId) { - return false; - } - - /** - * @see IRecentsAnimationController#detachNavigationBarFromApp - */ - @Override - public void detachNavigationBarFromApp(boolean moveHomeToTop) { - mExecutor.execute(() -> { - if (mTransition == null) return; - try { - ActivityTaskManager.getService().detachNavigationBarFromApp(mTransition); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to detach the navigation bar from app", e); - } - }); - } - - /** - * @see IRecentsAnimationController#animateNavigationBarToApp(long) - */ - @Override - public void animateNavigationBarToApp(long duration) { - } - }; - - /** Utility class to track the state of a task as-seen by recents. */ - private static class TaskState { - WindowContainerToken mToken; - ActivityManager.RunningTaskInfo mTaskInfo; - - /** The surface/leash of the task provided by Core. */ - SurfaceControl mTaskSurface; - - /** The (local) animation-leash created for this task. */ - SurfaceControl mLeash; - - TaskState(TransitionInfo.Change change, SurfaceControl leash) { - mToken = change.getContainer(); - mTaskInfo = change.getTaskInfo(); - mTaskSurface = change.getLeash(); - mLeash = leash; - } - - static int indexOf(ArrayList<TaskState> list, TransitionInfo.Change change) { - for (int i = list.size() - 1; i >= 0; --i) { - if (list.get(i).mToken.equals(change.getContainer())) { - return i; - } - } - return -1; - } - - public String toString() { - return "" + mToken + " : " + mLeash; - } - } - - /** - * An interface for a mixed handler to receive information about recents requests (since these - * come into this handler directly vs from WMCore request). - */ - public interface RecentsMixedHandler { - /** - * Called when a recents request comes in. The handler can add operations to outWCT. If - * the handler wants to "accept" the transition, it should return itself; otherwise, it - * should return `null`. - * - * If a mixed-handler accepts this recents, it will be the de-facto handler for this - * transition and is required to call the associated {@link #startAnimation}, - * {@link #mergeAnimation}, and {@link #onTransitionConsumed} methods. - */ - Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT); - - /** - * Reports the transition token associated with the accepted recents request. If there was - * a problem starting the request, this will be called with `null`. - */ - void setRecentsTransition(@Nullable IBinder transition); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 94b9e907fa76..7d5ab8428a3e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -31,7 +31,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.samePackage; -import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -89,7 +88,6 @@ import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropPolicy; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.recents.RecentTasksController; -import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; @@ -329,9 +327,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mTaskOrganizer.getRunningTaskInfo(taskId); } + /** Check task is under split or not by taskId. */ public boolean isTaskInSplitScreen(int taskId) { - return isSplitScreenVisible() - && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + return mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + } + + /** Check split is foreground and task is under split or not by taskId. */ + public boolean isTaskInSplitScreenForeground(int taskId) { + return isTaskInSplitScreen(taskId) && isSplitScreenVisible(); } public @SplitPosition int getSplitPosition(int taskId) { @@ -339,8 +342,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { - return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, - new WindowContainerTransaction()); + return moveToStage(taskId, sideStagePosition, new WindowContainerTransaction()); } /** @@ -351,13 +353,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator.updateSurfaces(transaction); } - private boolean moveToStage(int taskId, @StageType int stageType, - @SplitPosition int stagePosition, WindowContainerTransaction wct) { + private boolean moveToStage(int taskId, @SplitPosition int stagePosition, + WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); if (task == null) { throw new IllegalArgumentException("Unknown taskId" + taskId); } - return mStageCoordinator.moveToStage(task, stageType, stagePosition, wct); + return mStageCoordinator.moveToStage(task, stagePosition, wct); } public boolean removeFromSideStage(int taskId) { @@ -382,10 +384,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } public void enterSplitScreen(int taskId, boolean leftOrTop, WindowContainerTransaction wct) { - final int stageType = isSplitScreenVisible() ? STAGE_TYPE_UNDEFINED : STAGE_TYPE_SIDE; final int stagePosition = leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT; - moveToStage(taskId, stageType, stagePosition, wct); + moveToStage(taskId, stagePosition, wct); } public void exitSplitScreen(int toTopTaskId, @ExitReason int exitReason) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index a1eaf851da23..def945e53f9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -373,56 +373,43 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return STAGE_TYPE_UNDEFINED; } - boolean moveToStage(ActivityManager.RunningTaskInfo task, @StageType int stageType, - @SplitPosition int stagePosition, WindowContainerTransaction wct) { + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitPosition int stagePosition, + WindowContainerTransaction wct) { StageTaskListener targetStage; int sideStagePosition; - if (stageType == STAGE_TYPE_MAIN) { - targetStage = mMainStage; - sideStagePosition = reverseSplitPosition(stagePosition); - } else if (stageType == STAGE_TYPE_SIDE) { + if (isSplitScreenVisible()) { + // If the split screen is foreground, retrieves target stage based on position. + targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage; + sideStagePosition = mSideStagePosition; + } else { targetStage = mSideStage; sideStagePosition = stagePosition; - } else { - if (isSplitScreenVisible()) { - // If the split screen is activated, retrieves target stage based on position. - targetStage = stagePosition == mSideStagePosition ? mSideStage : mMainStage; - sideStagePosition = mSideStagePosition; - } else { - // Exit split if it running background. - exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); - - targetStage = mSideStage; - sideStagePosition = stagePosition; - } } if (!isSplitActive()) { - // prevent the fling divider to center transitioni if split screen didn't active. - mIsDropEntering = true; - } - - setSideStagePosition(sideStagePosition, wct); - final WindowContainerTransaction evictWct = new WindowContainerTransaction(); - targetStage.evictAllChildren(evictWct); - - // Apply surface bounds before animation start. - SurfaceControl.Transaction startT = mTransactionPool.acquire(); - if (startT != null) { - updateSurfaceBounds(mSplitLayout, startT, false /* applyResizingOffset */); - startT.apply(); - mTransactionPool.release(startT); + mSplitLayout.init(); + prepareEnterSplitScreen(wct, task, stagePosition); + mSyncQueue.queue(wct); + mSyncQueue.runInSync(t -> { + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + }); + } else { + setSideStagePosition(sideStagePosition, wct); + targetStage.addTask(task, wct); + targetStage.evictAllChildren(wct); + if (!isSplitScreenVisible()) { + final StageTaskListener anotherStage = targetStage == mMainStage + ? mSideStage : mMainStage; + anotherStage.reparentTopTask(wct); + anotherStage.evictAllChildren(wct); + wct.reorder(mRootTaskInfo.token, true); + } + setRootForceTranslucent(false, wct); + mSyncQueue.queue(wct); } - // reparent the task to an invisible split root will make the activity invisible. Reorder - // the root task to front to make the entering transition from pip to split smooth. - wct.reorder(mRootTaskInfo.token, true); - wct.reorder(targetStage.mRootTaskInfo.token, true); - targetStage.addTask(task, wct); - if (!evictWct.isEmpty()) { - wct.merge(evictWct, true /* transfer */); - } - mTaskOrganizer.applyTransaction(wct); + // Due to drag already pip task entering split by this method so need to reset flag here. + mIsDropEntering = false; return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index aa1e6ed8bcf3..aa851d1bd30f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -43,7 +43,6 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.StageCoordinator; import com.android.wm.shell.sysui.ShellInit; @@ -56,12 +55,10 @@ import java.util.Optional; * A handler for dealing with transitions involving multiple other handlers. For example: an * activity in split-screen going into PiP. */ -public class DefaultMixedHandler implements Transitions.TransitionHandler, - RecentsTransitionHandler.RecentsMixedHandler { +public class DefaultMixedHandler implements Transitions.TransitionHandler { private final Transitions mPlayer; private PipTransitionController mPipHandler; - private RecentsTransitionHandler mRecentsHandler; private StageCoordinator mSplitHandler; private static class MixedTransition { @@ -125,8 +122,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, public DefaultMixedHandler(@NonNull ShellInit shellInit, @NonNull Transitions player, Optional<SplitScreenController> splitScreenControllerOptional, - Optional<PipTouchHandler> pipTouchHandlerOptional, - Optional<RecentsTransitionHandler> recentsHandlerOptional) { + Optional<PipTouchHandler> pipTouchHandlerOptional) { mPlayer = player; if (Transitions.ENABLE_SHELL_TRANSITIONS && pipTouchHandlerOptional.isPresent() && splitScreenControllerOptional.isPresent()) { @@ -138,10 +134,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, if (mSplitHandler != null) { mSplitHandler.setMixedHandler(this); } - mRecentsHandler = recentsHandlerOptional.orElse(null); - if (mRecentsHandler != null) { - mRecentsHandler.addMixer(this); - } }, this); } } @@ -208,29 +200,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return null; } - @Override - public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) { - if (mRecentsHandler != null && mSplitHandler.isSplitActive()) { - return this; - } - return null; - } - - @Override - public void setRecentsTransition(IBinder transition) { - if (mSplitHandler.isSplitActive()) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " - + "Split-Screen is active, so treat it as Mixed."); - final MixedTransition mixed = new MixedTransition( - MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); - mixed.mLeftoversHandler = mRecentsHandler; - mActiveTransitions.add(mixed); - } else { - throw new IllegalStateException("Accepted a recents transition but don't know how to" - + " handle it"); - } - } - private TransitionInfo subCopy(@NonNull TransitionInfo info, @WindowManager.TransitionType int newType, boolean withChanges) { final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0); @@ -303,13 +272,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, info.getChanges().remove(i); } } + Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { + --mixed.mInFlightSubAnimations; + mixed.joinFinishArgs(wct, wctCB); + if (mixed.mInFlightSubAnimations > 0) return; + mActiveTransitions.remove(mixed); + finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); + }; if (pipChange == null) { if (mixed.mLeftoversHandler != null) { + mixed.mInFlightSubAnimations = 1; if (mixed.mLeftoversHandler.startAnimation(mixed.mTransition, - info, startTransaction, finishTransaction, (wct, wctCB) -> { - mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(wct, wctCB); - })) { + info, startTransaction, finishTransaction, finishCB)) { return true; } } @@ -318,13 +292,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Splitting PIP into a separate" + " animation because remote-animation likely doesn't support it"); - Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { - --mixed.mInFlightSubAnimations; - mixed.joinFinishArgs(wct, wctCB); - if (mixed.mInFlightSubAnimations > 0) return; - mActiveTransitions.remove(mixed); - finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB); - }; // Split the transition into 2 parts: the pip part and the rest. mixed.mInFlightSubAnimations = 2; // make a new startTransaction because pip's startEnterAnimation "consumes" it so @@ -594,8 +561,6 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mPipHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); - } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { - mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 039bde95815e..b8b6d5b96a93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -534,7 +534,7 @@ public class Transitions implements RemoteCallable<Transitions> { } if (info.getType() == TRANSIT_SLEEP) { - if (activeIdx > 0) { + if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) { // Sleep starts a process of forcing all prior transitions to finish immediately finishForSleep(null /* forceFinish */); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java index 86ca292399cb..fe0a3fb7b9dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java @@ -79,7 +79,7 @@ public class UnfoldBackgroundController { } private float[] getBackgroundColor(Context context) { - int colorInt = context.getResources().getColor(R.color.taskbar_background); + int colorInt = context.getResources().getColor(R.color.unfold_background); return new float[]{ (float) red(colorInt) / 255.0F, (float) green(colorInt) / 255.0F, diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt index ffdb87f190d5..5b06c9c3897d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt @@ -79,7 +79,8 @@ class EnterSplitScreenByDragFromAllApps(flicker: FlickerTest) : SplitScreenBase( @IwTest(focusArea = "sysui") @Presubmit @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false, + appExistAtStart = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt index 792e2b03522f..c8401831bdbc 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt @@ -82,7 +82,8 @@ class EnterSplitScreenByDragFromNotification(flicker: FlickerTest) : SplitScreen @IwTest(focusArea = "sysui") @Presubmit @Test - fun cujCompleted() = flicker.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false) + fun cujCompleted() = flicker.splitScreenEntered(primaryApp, sendNotificationApp, + fromOtherApp = false) @Presubmit @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 8b025cd7c246..919bf0665b5e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -185,7 +185,8 @@ public class BubbleDataTest extends ShellTestCase { Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class); appBubbleIntent.setPackage(mContext.getPackageName()); - mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mMainExecutor); + mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mock(Icon.class), + mMainExecutor); mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java index bc0d93a6810a..a6501f05475f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java @@ -54,7 +54,6 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java index a58620dfc6dc..172c263ab0f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduDialogLayoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -58,9 +58,8 @@ public class LetterboxEduDialogLayoutTest extends ShellTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mLayout = (LetterboxEduDialogLayout) - LayoutInflater.from(mContext).inflate(R.layout.letterbox_education_dialog_layout, - null); + mLayout = (LetterboxEduDialogLayout) LayoutInflater.from(mContext) + .inflate(R.layout.letterbox_education_dialog_layout, null); mDismissButton = mLayout.findViewById(R.id.letterbox_education_dialog_dismiss_button); mDialogContainer = mLayout.findViewById(R.id.letterbox_education_dialog_container); mLayout.setDismissOnClickListener(mDismissCallback); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java index 14190f18929c..47c9e06e8681 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/LetterboxEduWindowManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.compatui.letterboxedu; +package com.android.wm.shell.compatui; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -56,7 +56,6 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DockStateReader; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.compatui.DialogAnimationController; import com.android.wm.shell.transition.Transitions; import org.junit.After; @@ -400,15 +399,16 @@ public class LetterboxEduWindowManagerTest extends ShellTestCase { false, isDocked); } - private LetterboxEduWindowManager createWindowManager(boolean eligible, - int userId, boolean isTaskbarEduShowing) { + private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId, + boolean isTaskbarEduShowing) { return createWindowManager(eligible, userId, isTaskbarEduShowing, /* isDocked */false); } - private LetterboxEduWindowManager createWindowManager(boolean eligible, - int userId, boolean isTaskbarEduShowing, boolean isDocked) { + private LetterboxEduWindowManager createWindowManager(boolean eligible, int userId, + boolean isTaskbarEduShowing, boolean isDocked) { doReturn(isDocked).when(mDockStateReader).isDocked(); - LetterboxEduWindowManager windowManager = new LetterboxEduWindowManager(mContext, + LetterboxEduWindowManager + windowManager = new LetterboxEduWindowManager(mContext, createTaskInfo(eligible, userId), mSyncTransactionQueue, mTaskListener, createDisplayLayout(), mTransitions, mOnDismissCallback, mAnimationController, mDockStateReader); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 2e2e49e40030..eda6fdc4dbd4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -145,39 +145,48 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testMoveToStage() { + public void testMoveToStage_splitActiveBackground() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mSideStage).addTask(eq(task), eq(wct)); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); + } + + @Test + public void testMoveToStage_splitActiveForeground() { + when(mStageCoordinator.isSplitActive()).thenReturn(true); + when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); + // Assume current side stage is top or left. + mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); + final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); - mStageCoordinator.moveToStage(task, STAGE_TYPE_MAIN, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mMainStage).addTask(eq(task), eq(wct)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); - mStageCoordinator.moveToStage(task, STAGE_TYPE_SIDE, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); - assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_TOP_OR_LEFT, wct); + verify(mSideStage).addTask(eq(task), eq(wct)); + assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getSideStagePosition()); + assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getMainStagePosition()); } @Test - public void testMoveToUndefinedStage() { + public void testMoveToStage_splitInctive() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); + final WindowContainerTransaction wct = new WindowContainerTransaction(); - // Verify move to undefined stage while split screen not activated moves task to side stage. - when(mStageCoordinator.isSplitScreenVisible()).thenReturn(false); - mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); - mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_BOTTOM_OR_RIGHT, - new WindowContainerTransaction()); - verify(mSideStage).addTask(eq(task), any(WindowContainerTransaction.class)); + mStageCoordinator.moveToStage(task, SPLIT_POSITION_BOTTOM_OR_RIGHT, wct); + verify(mStageCoordinator).prepareEnterSplitScreen(eq(wct), eq(task), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT)); assertEquals(SPLIT_POSITION_BOTTOM_OR_RIGHT, mStageCoordinator.getSideStagePosition()); - - // Verify move to undefined stage after split screen activated moves task based on position. - when(mStageCoordinator.isSplitScreenVisible()).thenReturn(true); - assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); - mStageCoordinator.moveToStage(task, STAGE_TYPE_UNDEFINED, SPLIT_POSITION_TOP_OR_LEFT, - new WindowContainerTransaction()); - verify(mMainStage).addTask(eq(task), any(WindowContainerTransaction.class)); - assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition()); } @Test diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 8ab715965082..8efd180b6c93 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6251,6 +6251,7 @@ public class AudioManager { * Volume behavior for an audio device where no software attenuation is applied, and * the volume is kept synchronized between the host and the device itself through a * device-specific protocol such as BT AVRCP. + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; @@ -6261,6 +6262,7 @@ public class AudioManager { * device-specific protocol (such as for hearing aids), based on the audio mode (e.g. * normal vs in phone call). * @see #setMode(int) + * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; @@ -6270,11 +6272,6 @@ public class AudioManager { * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have * no effect, or an unreliable effect. - * - * {@link #DEVICE_VOLUME_BEHAVIOR_FULL} will be returned instead by - * {@link #getDeviceVolumeBehavior} for target SDK versions before U. - * - * @see #RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; @@ -6316,27 +6313,18 @@ public class AudioManager { public @interface AbsoluteDeviceVolumeBehavior {} /** - * Volume behaviors that can be set with {@link #setDeviceVolumeBehavior}. - * @hide - */ - @IntDef({ - DEVICE_VOLUME_BEHAVIOR_VARIABLE, - DEVICE_VOLUME_BEHAVIOR_FULL, - DEVICE_VOLUME_BEHAVIOR_FIXED, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface SettableDeviceVolumeBehavior {} - - /** * @hide - * Throws IAE on a non-settable volume behavior value + * Throws IAE on an invalid volume behavior value * @param volumeBehavior behavior value to check */ - public static void enforceSettableVolumeBehavior(int volumeBehavior) { + public static void enforceValidVolumeBehavior(int volumeBehavior) { switch (volumeBehavior) { case DEVICE_VOLUME_BEHAVIOR_VARIABLE: case DEVICE_VOLUME_BEHAVIOR_FULL: case DEVICE_VOLUME_BEHAVIOR_FIXED: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: + case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: return; default: throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior); @@ -6346,8 +6334,11 @@ public class AudioManager { /** * @hide * Sets the volume behavior for an audio output device. - * - * @see SettableDeviceVolumeBehavior + * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE + * @see #DEVICE_VOLUME_BEHAVIOR_FULL + * @see #DEVICE_VOLUME_BEHAVIOR_FIXED + * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE + * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE * @param device the device to be affected * @param deviceVolumeBehavior one of the device behaviors */ @@ -6357,10 +6348,10 @@ public class AudioManager { Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @SettableDeviceVolumeBehavior int deviceVolumeBehavior) { + @DeviceVolumeBehavior int deviceVolumeBehavior) { // verify arguments (validity of device type is enforced in server) Objects.requireNonNull(device); - enforceSettableVolumeBehavior(deviceVolumeBehavior); + enforceValidVolumeBehavior(deviceVolumeBehavior); // communicate with service final IAudioService service = getService(); try { diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java index 9b238e172247..6744359d12d7 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -49,6 +49,7 @@ import com.android.internal.util.ArrayUtils; import libcore.io.IoUtils; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; @@ -255,17 +256,19 @@ public class ThumbnailUtils { // get orientation if (MediaFile.isExifMimeType(mimeType)) { - exif = new ExifInterface(file); - switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) { - case ExifInterface.ORIENTATION_ROTATE_90: - orientation = 90; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - orientation = 180; - break; - case ExifInterface.ORIENTATION_ROTATE_270: - orientation = 270; - break; + try (FileInputStream is = new FileInputStream(file)) { + exif = new ExifInterface(is.getFD()); + switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) { + case ExifInterface.ORIENTATION_ROTATE_90: + orientation = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + orientation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + orientation = 270; + break; + } } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index b32fe3fef00d..28f9453a48a2 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -467,19 +467,19 @@ class CredentialManagerRepo( GetCredentialRequest.Builder( Bundle() ).addCredentialOption( - CredentialOption( + CredentialOption.Builder( passwordOption.type, passwordOption.requestData, passwordOption.candidateQueryData, - passwordOption.isSystemProviderRequired - ) + ).setIsSystemProviderRequired(passwordOption.isSystemProviderRequired) + .build() ).addCredentialOption( - CredentialOption( + CredentialOption.Builder( passkeyOption.type, passkeyOption.requestData, passkeyOption.candidateQueryData, - passkeyOption.isSystemProviderRequired - ) + ).setIsSystemProviderRequired(passkeyOption.isSystemProviderRequired) + .build() ).build(), "com.google.android.youtube" ) diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml index 6669e358eb08..a2118fac4231 100644 --- a/packages/PackageInstaller/res/values/strings.xml +++ b/packages/PackageInstaller/res/values/strings.xml @@ -37,9 +37,8 @@ <string name="install_confirm_question">Do you want to install this app?</string> <!-- Message for updating an existing app [CHAR LIMIT=NONE] --> <string name="install_confirm_question_update">Do you want to update this app?</string> - <!-- TODO(b/244413073) Revise the description after getting UX input and UXR on this. --> - <!-- Message for updating an existing app with update owner reminder [DO NOT TRANSLATE][CHAR LIMIT=NONE] --> - <string name="install_confirm_question_update_owner_reminder">Updates to this app are currently managed by <xliff:g id="existing_update_owner">%1$s</xliff:g>.\n\nDo you want to install this update from <xliff:g id="new_update_owner">%2$s</xliff:g>?</string> + <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] --> + <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string> <!-- [CHAR LIMIT=100] --> <string name="install_failed">App not installed.</string> <!-- Reason displayed when installation fails because the package was blocked @@ -82,6 +81,8 @@ <!-- [CHAR LIMIT=15] --> <string name="ok">OK</string> + <!-- [CHAR LIMIT=30] --> + <string name="update_anyway">Update anyway</string> <!-- [CHAR LIMIT=15] --> <string name="manage_applications">Manage apps</string> <!-- [CHAR LIMIT=30] --> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index d41cfbc2d4ce..c81e75bbab7a 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -148,10 +148,11 @@ public class PackageInstallerActivity extends AlertActivity { && mPendingUserActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP) { viewToEnable.setText( getString(R.string.install_confirm_question_update_owner_reminder, - existingUpdateOwnerLabel, requestedUpdateOwnerLabel)); + requestedUpdateOwnerLabel, existingUpdateOwnerLabel)); + mOk.setText(R.string.update_anyway); + } else { + mOk.setText(R.string.update); } - - mOk.setText(R.string.update); } else { // This is a new application with no permissions. viewToEnable = requireViewById(R.id.install_confirm_question); diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index f6bb3cc271fe..47ac2df67c76 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -109,7 +109,7 @@ internal fun CustomizedLargeTopAppBar( scrollBehavior: TopAppBarScrollBehavior? = null, ) { TwoRowsTopAppBar( - title = { Title(title = title, maxLines = 2) }, + title = { Title(title = title, maxLines = 3) }, titleTextStyle = MaterialTheme.typography.displaySmall, smallTitleTextStyle = MaterialTheme.typography.titleMedium, titleBottomPadding = LargeTitleBottomPadding, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt index c6090042895e..9f33fcb0052b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt @@ -62,10 +62,12 @@ class AppOpsController( } val permission = AppOpsManager.opToPermission(op) - packageManager.updatePermissionFlags(permission, app.packageName, - PackageManager.FLAG_PERMISSION_USER_SET, PackageManager.FLAG_PERMISSION_USER_SET, - UserHandle.getUserHandleForUid(app.uid)) - + if (permission != null) { + packageManager.updatePermissionFlags(permission, app.packageName, + PackageManager.FLAG_PERMISSION_USER_SET, + PackageManager.FLAG_PERMISSION_USER_SET, + UserHandle.getUserHandleForUid(app.uid)) + } _mode.postValue(mode) } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index 3fe12b3dab32..85623b26c589 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -52,6 +52,7 @@ public class SystemSettingsValidators { || (val == BatteryManager.BATTERY_PLUGGED_AC) || (val == BatteryManager.BATTERY_PLUGGED_USB) || (val == BatteryManager.BATTERY_PLUGGED_WIRELESS) + || (val == BatteryManager.BATTERY_PLUGGED_DOCK) || (val == (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB)) @@ -64,7 +65,13 @@ public class SystemSettingsValidators { || (val == (BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB - | BatteryManager.BATTERY_PLUGGED_WIRELESS)); + | BatteryManager.BATTERY_PLUGGED_WIRELESS)) + || (val + == (BatteryManager.BATTERY_PLUGGED_AC + | BatteryManager.BATTERY_PLUGGED_DOCK)) + || (val + == (BatteryManager.BATTERY_PLUGGED_USB + | BatteryManager.BATTERY_PLUGGED_DOCK)); } catch (NumberFormatException e) { return false; } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java index a8eeec3c2f24..80030f7a7a47 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java @@ -50,10 +50,6 @@ final class GenerationRegistry { @GuardedBy("mLock") private final ArrayMap<Integer, ArrayMap<String, Integer>> mKeyToIndexMapMap = new ArrayMap<>(); - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) - // Maximum number of backing stores allowed - static final int NUM_MAX_BACKING_STORE = 8; - @GuardedBy("mLock") private int mNumBackingStore = 0; @@ -65,8 +61,24 @@ final class GenerationRegistry { // The generation number is only increased when a new non-predefined setting is inserted private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = ""; - public GenerationRegistry(Object lock) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + // Minimum number of backing stores; supports 3 users + static final int MIN_NUM_BACKING_STORE = 8; + // Maximum number of backing stores; supports 18 users + static final int MAX_NUM_BACKING_STORE = 38; + + private final int mMaxNumBackingStore; + + GenerationRegistry(Object lock, int maxNumUsers) { mLock = lock; + // Add some buffer to maxNumUsers to accommodate corner cases when the actual number of + // users in the system exceeds the limit + maxNumUsers = maxNumUsers + 2; + // Number of backing stores needed for N users is (N + N + 1 + 1) = N * 2 + 2 + // N Secure backing stores and N System backing stores, 1 Config and 1 Global for all users + // However, we always make sure that at least 3 users and at most 18 users are supported. + mMaxNumBackingStore = Math.min(Math.max(maxNumUsers * 2 + 2, MIN_NUM_BACKING_STORE), + MAX_NUM_BACKING_STORE); } /** @@ -195,7 +207,7 @@ final class GenerationRegistry { } if (backingStore == null) { try { - if (mNumBackingStore >= NUM_MAX_BACKING_STORE) { + if (mNumBackingStore >= mMaxNumBackingStore) { if (DEBUG) { Slog.e(LOG_TAG, "Error creating backing store - at capacity"); } @@ -275,4 +287,9 @@ final class GenerationRegistry { } return -1; } + + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + int getMaxNumBackingStores() { + return mMaxNumBackingStore; + } }
\ No newline at end of file diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7e89bfce7255..721b3c49b17c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2831,7 +2831,7 @@ public class SettingsProvider extends ContentProvider { public SettingsRegistry() { mHandler = new MyHandler(getContext().getMainLooper()); - mGenerationRegistry = new GenerationRegistry(mLock); + mGenerationRegistry = new GenerationRegistry(mLock, UserManager.getMaxSupportedUsers()); mBackupManager = new BackupManager(getContext()); } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 47abb35d01ab..e0e37200219c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -265,7 +265,6 @@ public class SettingsBackupTest { Settings.Global.ENABLE_DELETION_HELPER_NO_THRESHOLD_TOGGLE, Settings.Global.ENABLE_DISKSTATS_LOGGING, Settings.Global.ENABLE_EPHEMERAL_FEATURE, - Settings.Global.ENABLE_RESTRICTED_BUCKET, Settings.Global.ENABLE_TARE, Settings.Global.DYNAMIC_POWER_SAVINGS_ENABLED, Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java index 586d6f73baad..12865f452e22 100644 --- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java +++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java @@ -36,7 +36,7 @@ import java.io.IOException; public class GenerationRegistryTest { @Test public void testGenerationsWithRegularSetting() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -93,7 +93,7 @@ public class GenerationRegistryTest { @Test public void testGenerationsWithConfigSetting() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); final String prefix = "test_namespace/"; final int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0); @@ -110,10 +110,10 @@ public class GenerationRegistryTest { @Test public void testMaxNumBackingStores() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); - for (int i = 0; i < GenerationRegistry.NUM_MAX_BACKING_STORE; i++) { + for (int i = 0; i < generationRegistry.getMaxNumBackingStores(); i++) { b.clear(); final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, i); generationRegistry.addGenerationData(b, key, testSecureSetting); @@ -121,7 +121,7 @@ public class GenerationRegistryTest { } b.clear(); final int key = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, - GenerationRegistry.NUM_MAX_BACKING_STORE + 1); + generationRegistry.getMaxNumBackingStores() + 1); generationRegistry.addGenerationData(b, key, testSecureSetting); // Should fail to add generation because the number of backing stores has reached limit checkBundle(b, -1, -1, true); @@ -133,7 +133,7 @@ public class GenerationRegistryTest { @Test public void testMaxSizeBackingStore() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -153,7 +153,7 @@ public class GenerationRegistryTest { @Test public void testUnsetSettings() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 1); final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0); final String testSecureSetting = "test_secure_setting"; Bundle b = new Bundle(); @@ -172,7 +172,7 @@ public class GenerationRegistryTest { @Test public void testGlobalSettings() throws IOException { - final GenerationRegistry generationRegistry = new GenerationRegistry(new Object()); + final GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 2); final int globalKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_GLOBAL, 0); final String testGlobalSetting = "test_global_setting"; final Bundle b = new Bundle(); @@ -188,6 +188,18 @@ public class GenerationRegistryTest { assertThat(array).isEqualTo(array2); } + @Test + public void testNumberOfBackingStores() { + GenerationRegistry generationRegistry = new GenerationRegistry(new Object(), 0); + // Test that the capacity of the backing stores is always valid + assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( + GenerationRegistry.MIN_NUM_BACKING_STORE); + generationRegistry = new GenerationRegistry(new Object(), 100); + // Test that the capacity of the backing stores is always valid + assertThat(generationRegistry.getMaxNumBackingStores()).isEqualTo( + GenerationRegistry.MAX_NUM_BACKING_STORE); + } + private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull) throws IOException { final MemoryIntArray array = getArray(b); diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt index b9e38cf3cc60..99fe26ce1f3b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/multishade/ui/composable/MultiShade.kt @@ -53,6 +53,7 @@ fun MultiShade( modifier: Modifier = Modifier, ) { val isScrimEnabled: Boolean by viewModel.isScrimEnabled.collectAsState() + val scrimAlpha: Float by viewModel.scrimAlpha.collectAsState() // TODO(b/273298030): find a different way to get the height constraint from its parent. BoxWithConstraints(modifier = modifier) { @@ -61,7 +62,7 @@ fun MultiShade( Scrim( modifier = Modifier.fillMaxSize(), remoteTouch = viewModel::onScrimTouched, - alpha = { viewModel.scrimAlpha.value }, + alpha = { scrimAlpha }, isScrimEnabled = isScrimEnabled, ) Shade( diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index a3171784903f..762dcdced9c4 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -55,7 +55,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:textSize="@dimen/chipbar_text_size" - android:textColor="@android:color/system_accent2_900" + android:textColor="@color/chipbar_text_and_icon_color" android:alpha="0.0" /> diff --git a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml index b374074958cb..80f5d87bec00 100644 --- a/packages/SystemUI/res/layout/status_bar_user_chip_container.xml +++ b/packages/SystemUI/res/layout/status_bar_user_chip_container.xml @@ -24,7 +24,7 @@ android:orientation="horizontal" android:layout_marginEnd="@dimen/status_bar_user_chip_end_margin" android:background="@drawable/status_bar_user_chip_bg" - android:visibility="visible" > + android:visibility="gone" > <ImageView android:id="@+id/current_user_avatar" android:layout_width="@dimen/status_bar_user_chip_avatar_size" android:layout_height="@dimen/status_bar_user_chip_avatar_size" diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 62e8c5f0df82..31071ca28a75 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -231,6 +231,9 @@ <color name="people_tile_background">@color/material_dynamic_secondary95</color> + <!-- Chipbar --> + <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color> + <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt index 3efdc5acb00c..4931b257ad18 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt @@ -192,4 +192,4 @@ class UnfoldMoveFromCenterAnimator @JvmOverloads constructor( ) } -private const val TRANSLATION_PERCENTAGE = 0.3f +private const val TRANSLATION_PERCENTAGE = 0.08f diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 4aaa566eb852..3b9060ad0ac3 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -259,7 +259,7 @@ constructor( largeTimeListener?.update(shouldTimeListenerRun) } - override fun onTimeFormatChanged(timeFormat: String) { + override fun onTimeFormatChanged(timeFormat: String?) { clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 5ec59abafe13..7b781cef6717 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -22,6 +22,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; +import android.annotation.Nullable; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; @@ -458,6 +459,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mView.setClock(clock, mStatusBarStateController.getState()); } + @Nullable private ClockController getClock() { return mClockEventController.getClock(); } @@ -510,7 +512,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** Gets the animations for the current clock. */ + @Nullable public ClockAnimations getClockAnimations() { - return getClock().getAnimations(); + ClockController clock = getClock(); + return clock == null ? null : clock.getAnimations(); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 7255383049da..1a572b729a6e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -260,6 +260,14 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard */ @Override public void finish(boolean strongAuth, int targetUserId) { + if (mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD) + && !mKeyguardStateController.canDismissLockScreen() && !strongAuth) { + Log.e(TAG, + "Tried to dismiss keyguard when lockscreen is not dismissible and user " + + "was not authenticated with a primary security method " + + "(pin/password/pattern)."); + return; + } // If there's a pending runnable because the user interacted with a widget // and we're leaving keyguard, then run it. boolean deferKeyguardDone = false; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 0e2f8f07cb01..f2f0c597b86c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -69,6 +69,7 @@ import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STARTED_WAK import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED; import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_UPDATED_USER_SWITCHING; import static com.android.systemui.DejankUtils.whitelistIpcs; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED; import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN; import android.annotation.AnyThread; @@ -1610,7 +1611,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab requestActiveUnlock( ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT, "assistant", - false); + /* dismissKeyguard */ true); } } @@ -1881,6 +1882,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab updateFaceListeningState(BIOMETRIC_ACTION_STOP, FACE_AUTH_UPDATED_POSTURE_CHANGED); } + if (mPostureState == DEVICE_POSTURE_OPENED) { + mLogger.d("Posture changed to open - attempting to request active unlock"); + requestActiveUnlockFromWakeReason(PowerManager.WAKE_REASON_UNFOLD_DEVICE, + false); + } } }; @@ -2007,26 +2013,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason); updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_STARTED_WAKING_UP); - - final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin = - mActiveUnlockConfig.isWakeupConsideredUnlockIntent(pmWakeReason) - ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT - : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE; - final String reason = "wakingUp - " + PowerManager.wakeReasonToString(pmWakeReason); - if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(pmWakeReason)) { - requestActiveUnlockDismissKeyguard( - requestOrigin, - reason - ); - } else { - requestActiveUnlock( - requestOrigin, - reason - ); - } } else { mLogger.logSkipUpdateFaceListeningOnWakeup(pmWakeReason); } + requestActiveUnlockFromWakeReason(pmWakeReason, true); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -2660,6 +2650,32 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } + private void requestActiveUnlockFromWakeReason(@PowerManager.WakeReason int wakeReason, + boolean powerManagerWakeup) { + if (!mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(wakeReason)) { + mLogger.logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason); + return; + } + + final ActiveUnlockConfig.ActiveUnlockRequestOrigin requestOrigin = + mActiveUnlockConfig.isWakeupConsideredUnlockIntent(wakeReason) + ? ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + : ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE; + final String reason = "wakingUp - " + PowerManager.wakeReasonToString(wakeReason) + + " powerManagerWakeup=" + powerManagerWakeup; + if (mActiveUnlockConfig.shouldWakeupForceDismissKeyguard(wakeReason)) { + requestActiveUnlockDismissKeyguard( + requestOrigin, + reason + ); + } else { + requestActiveUnlock( + requestOrigin, + reason + ); + } + } + /** * Attempts to trigger active unlock from trust agent. */ diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 51628076953b..16618064f249 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -62,6 +62,16 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { ) } + fun logActiveUnlockRequestSkippedForWakeReasonDueToFaceConfig(wakeReason: Int) { + logBuffer.log( + "ActiveUnlock", + DEBUG, + { int1 = wakeReason }, + { "Skip requesting active unlock from wake reason that doesn't trigger face auth" + + " reason=${PowerManager.wakeReasonToString(int1)}" } + ) + } + fun logAuthInterruptDetected(active: Boolean) { logBuffer.log(TAG, DEBUG, { bool1 = active }, { "onAuthInterruptDetected($bool1)" }) } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 61d039bf53c6..c98a62f36656 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -195,7 +195,7 @@ constructor( scope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> if (isVisible) { - show(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD) } else { hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER) } @@ -436,13 +436,17 @@ private fun LottieAnimationView.addOverlayDynamicColor( @BiometricOverlayConstants.ShowReason reason: Int ) { fun update() { - val c = context.getColor(R.color.biometric_dialog_accent) - val chevronFill = context.getColor(R.color.sfps_chevron_fill) val isKeyguard = reason == REASON_AUTH_KEYGUARD if (isKeyguard) { + val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color + val chevronFill = + com.android.settingslib.Utils.getColorAttrDefaultColor( + context, + android.R.attr.textColorPrimaryInverse + ) for (key in listOf(".blue600", ".blue400")) { addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) { - PorterDuffColorFilter(c, PorterDuff.Mode.SRC_ATOP) + PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) } } addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) { diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt index 5dabbbb81701..6a6c3eb05399 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt @@ -16,10 +16,10 @@ package com.android.systemui.common.shared.model -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes /** Models an icon with a specific tint. */ data class TintedIcon( val icon: Icon, - @AttrRes val tintAttr: Int?, + @ColorRes val tint: Int?, ) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt index dea8cfda80c3..bcc5932dcf30 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt @@ -17,15 +17,14 @@ package com.android.systemui.common.ui.binder import android.widget.ImageView -import com.android.settingslib.Utils import com.android.systemui.common.shared.model.TintedIcon object TintedIconViewBinder { /** * Binds the given tinted icon to the view. * - * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint - * *will* be reset to null. + * [TintedIcon.tint] will always be applied, meaning that if it is null, then the tint *will* be + * reset to null. */ fun bind( tintedIcon: TintedIcon, @@ -33,8 +32,8 @@ object TintedIconViewBinder { ) { IconViewBinder.bind(tintedIcon.icon, view) view.imageTintList = - if (tintedIcon.tintAttr != null) { - Utils.getColorAttr(view.context, tintedIcon.tintAttr) + if (tintedIcon.tint != null) { + view.resources.getColorStateList(tintedIcon.tint, view.context.theme) } else { null } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index ee12db8d07b1..868e52784290 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -608,6 +608,7 @@ class ControlsUiControllerImpl @Inject constructor ( if (items.size == 1) { spinner.setBackground(null) anchor.setOnClickListener(null) + anchor.isClickable = false return } else { spinner.background = parent.context.resources diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java index 055cd52b23d6..7f567aa334a6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamMonitor.java @@ -23,6 +23,7 @@ import android.util.Log; import com.android.systemui.CoreStartable; import com.android.systemui.dreams.callbacks.DreamStatusBarStateCallback; import com.android.systemui.dreams.conditions.DreamCondition; +import com.android.systemui.flags.RestartDozeListener; import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.condition.ConditionalCoreStartable; @@ -39,17 +40,19 @@ public class DreamMonitor extends ConditionalCoreStartable { private final Monitor mConditionMonitor; private final DreamCondition mDreamCondition; private final DreamStatusBarStateCallback mCallback; + private RestartDozeListener mRestartDozeListener; @Inject public DreamMonitor(Monitor monitor, DreamCondition dreamCondition, @Named(DREAM_PRETEXT_MONITOR) Monitor pretextMonitor, - DreamStatusBarStateCallback callback) { + DreamStatusBarStateCallback callback, + RestartDozeListener restartDozeListener) { super(pretextMonitor); mConditionMonitor = monitor; mDreamCondition = dreamCondition; mCallback = callback; - + mRestartDozeListener = restartDozeListener; } @Override @@ -61,5 +64,8 @@ public class DreamMonitor extends ConditionalCoreStartable { mConditionMonitor.addSubscription(new Monitor.Subscription.Builder(mCallback) .addCondition(mDreamCondition) .build()); + + mRestartDozeListener.init(); + mRestartDozeListener.maybeRestartSleep(); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java index 2befce7065ec..5bbfbda82944 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/conditions/DreamCondition.java @@ -15,6 +15,7 @@ */ package com.android.systemui.dreams.conditions; +import android.app.DreamManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -30,6 +31,7 @@ import javax.inject.Inject; */ public class DreamCondition extends Condition { private final Context mContext; + private final DreamManager mDreamManager; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -39,8 +41,10 @@ public class DreamCondition extends Condition { }; @Inject - public DreamCondition(Context context) { + public DreamCondition(Context context, + DreamManager dreamManager) { mContext = context; + mDreamManager = dreamManager; } private void processIntent(Intent intent) { @@ -62,8 +66,8 @@ public class DreamCondition extends Condition { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_DREAMING_STARTED); filter.addAction(Intent.ACTION_DREAMING_STOPPED); - final Intent stickyIntent = mContext.registerReceiver(mReceiver, filter); - processIntent(stickyIntent); + mContext.registerReceiver(mReceiver, filter); + updateCondition(mDreamManager.isDreaming()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt index 06ca0adfa928..28c45b874b24 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt @@ -22,7 +22,6 @@ import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.util.InitializationChecker -import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -38,8 +37,6 @@ constructor( private val featureFlags: FeatureFlagsDebug, private val broadcastSender: BroadcastSender, private val initializationChecker: InitializationChecker, - private val restartDozeListener: RestartDozeListener, - private val delayableExecutor: DelayableExecutor ) : CoreStartable { init { @@ -55,9 +52,6 @@ constructor( // protected broadcast should only be sent for the main process val intent = Intent(FlagManager.ACTION_SYSUI_STARTED) broadcastSender.sendBroadcast(intent) - - restartDozeListener.init() - delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000) } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt index 133e67f2822b..f97112d384be 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseStartable.kt @@ -18,8 +18,6 @@ package com.android.systemui.flags import com.android.systemui.CoreStartable import com.android.systemui.dump.DumpManager -import com.android.systemui.util.InitializationChecker -import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -31,9 +29,6 @@ class FeatureFlagsReleaseStartable constructor( dumpManager: DumpManager, featureFlags: FeatureFlags, - private val initializationChecker: InitializationChecker, - private val restartDozeListener: RestartDozeListener, - private val delayableExecutor: DelayableExecutor ) : CoreStartable { init { @@ -42,12 +37,7 @@ constructor( } } - override fun start() { - if (initializationChecker.initializeComponents()) { - restartDozeListener.init() - delayableExecutor.executeDelayed({ restartDozeListener.maybeRestartSleep() }, 1000) - } - } + override fun start() {} } @Module diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 71c92d22762d..8f3f64fbe50a 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -228,6 +228,11 @@ object Flags { @JvmField val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true) + /** Whether to inflate the bouncer view on a background thread. */ + // TODO(b/273341787): Tracking Bug + @JvmField + val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -410,7 +415,7 @@ object Flags { @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") // TODO(b/270882464): Tracking Bug - val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true) + val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2") // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") @@ -678,6 +683,12 @@ object Flags { val ENABLE_DARK_VIGNETTE_WHEN_FOLDING = unreleasedFlag(2700, "enable_dark_vignette_when_folding") + // TODO(b/265764985): Tracking Bug + @Keep + @JvmField + val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS = + unreleasedFlag(2701, "enable_unfold_status_bar_animations") + // TODO(b259590361): Tracking bug val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release") diff --git a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt index bd74f4e5daab..b49d60dbdde1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/RestartDozeListener.kt @@ -20,7 +20,9 @@ import android.os.PowerManager import android.util.Log import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -33,6 +35,7 @@ constructor( private val statusBarStateController: StatusBarStateController, private val powerManager: PowerManager, private val systemClock: SystemClock, + @Background val bgExecutor: DelayableExecutor, ) { companion object { @@ -44,7 +47,7 @@ constructor( val listener = object : StatusBarStateController.StateListener { override fun onDreamingChanged(isDreaming: Boolean) { - settings.putBool(RESTART_NAP_KEY, isDreaming) + storeSleepState(isDreaming) } } @@ -62,11 +65,19 @@ constructor( } fun maybeRestartSleep() { - if (settings.getBool(RESTART_NAP_KEY, false)) { - Log.d("RestartDozeListener", "Restarting sleep state") - powerManager.wakeUp(systemClock.uptimeMillis()) - powerManager.goToSleep(systemClock.uptimeMillis()) - settings.putBool(RESTART_NAP_KEY, false) - } + bgExecutor.executeDelayed( + { + if (settings.getBool(RESTART_NAP_KEY, false)) { + Log.d("RestartDozeListener", "Restarting sleep state") + powerManager.wakeUp(systemClock.uptimeMillis()) + powerManager.goToSleep(systemClock.uptimeMillis()) + } + }, + 1000 + ) + } + + private fun storeSleepState(sleeping: Boolean) { + bgExecutor.execute { settings.putBool(RESTART_NAP_KEY, sleeping) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index d745a19e8549..aad4a2dd1af7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.SystemClock @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.Flow class AlternateBouncerInteractor @Inject constructor( + private val statusBarStateController: StatusBarStateController, private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, private val biometricSettingsRepository: BiometricSettingsRepository, @@ -49,6 +51,17 @@ constructor( var receivedDownTouch = false val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + private val keyguardStateControllerCallback: KeyguardStateController.Callback = + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + maybeHide() + } + } + + init { + keyguardStateController.addCallback(keyguardStateControllerCallback) + } + /** * Sets the correct bouncer states to show the alternate bouncer if it can show. * @@ -109,7 +122,8 @@ constructor( biometricSettingsRepository.isStrongBiometricAllowed.value && biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value && !deviceEntryFingerprintAuthRepository.isLockedOut.value && - !keyguardStateController.isUnlocked + !keyguardStateController.isUnlocked && + !statusBarStateController.isDozing } else { legacyAlternateBouncer != null && keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true) @@ -129,6 +143,12 @@ constructor( } } + private fun maybeHide() { + if (isVisibleState() && !canShowAlternateBouncerForFingerprint()) { + hide() + } + } + companion object { private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L private const val NOT_VISIBLE = -1L diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 889adc77196a..5704f8861f0e 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -160,6 +160,14 @@ public class LogModule { return factory.create("QSLog", 700 /* maxSize */, false /* systrace */); } + /** Provides a logging buffer for logs related to Quick Settings configuration. */ + @Provides + @SysUISingleton + @QSConfigLog + public static LogBuffer provideQSConfigLogBuffer(LogBufferFactory factory) { + return factory.create("QSConfigLog", 100 /* maxSize */, true /* systrace */); + } + /** Provides a logging buffer for {@link com.android.systemui.broadcast.BroadcastDispatcher} */ @Provides @SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java new file mode 100644 index 000000000000..295bf88d498f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/QSConfigLog.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.plugins.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** A {@link LogBuffer} for QS configuration changed messages. */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface QSConfigLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index ee93c3788243..dbc2a5ec4e0a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -19,12 +19,13 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.TintedIcon +import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT /** Utility methods for media tap-to-transfer. */ class MediaTttUtils { @@ -78,7 +79,7 @@ class MediaTttUtils { return IconInfo( contentDescription, MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)), - tintAttr = null, + tint = null, isAppIcon = true ) } catch (e: PackageManager.NameNotFoundException) { @@ -96,7 +97,7 @@ class MediaTttUtils { ) }, MediaTttIcon.Resource(R.drawable.ic_cast), - tintAttr = android.R.attr.textColorPrimary, + tint = DEFAULT_ICON_TINT, isAppIcon = false ) } @@ -107,7 +108,7 @@ class MediaTttUtils { data class IconInfo( val contentDescription: ContentDescription, val icon: MediaTttIcon, - @AttrRes val tintAttr: Int?, + @ColorRes val tint: Int?, /** * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. */ @@ -120,7 +121,7 @@ data class IconInfo( is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription) is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription) } - return TintedIcon(iconOutput, tintAttr) + return TintedIcon(iconOutput, tint) } } diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt index b9f6d83d8406..ebb8639b8922 100644 --- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.multishade.data.model.MultiShadeInteractionModel import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.shared.math.isZero import com.android.systemui.multishade.shared.model.ProxiedInputModel import com.android.systemui.multishade.shared.model.ShadeConfig import com.android.systemui.multishade.shared.model.ShadeId @@ -63,6 +64,10 @@ constructor( } } + /** Whether any shade is expanded, even a little bit. */ + val isAnyShadeExpanded: Flow<Boolean> = + maxShadeExpansion.map { maxExpansion -> !maxExpansion.isZero() }.distinctUntilChanged() + /** * A _processed_ version of the proxied input flow. * diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt new file mode 100644 index 000000000000..ff7c9015eef4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.multishade.domain.interactor + +import android.content.Context +import android.view.MotionEvent +import android.view.ViewConfiguration +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.multishade.shared.model.ProxiedInputModel +import javax.inject.Inject +import kotlin.math.abs +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn + +/** + * Encapsulates business logic to handle [MotionEvent]-based user input. + * + * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s + * into the newer multi-shade framework for processing. + */ +class MultiShadeMotionEventInteractor +@Inject +constructor( + @Application private val applicationContext: Context, + @Application private val applicationScope: CoroutineScope, + private val interactor: MultiShadeInteractor, +) { + + private val isAnyShadeExpanded: StateFlow<Boolean> = + interactor.isAnyShadeExpanded.stateIn( + scope = applicationScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) + + private var interactionState: InteractionState? = null + + /** + * Returns `true` if the given [MotionEvent] and the rest of events in this gesture should be + * passed to this interactor's [onTouchEvent] method. + * + * Note: the caller should continue to pass [MotionEvent] instances into this method, even if it + * returns `false` as the gesture may be intercepted mid-stream. + */ + fun shouldIntercept(event: MotionEvent): Boolean { + if (isAnyShadeExpanded.value) { + // If any shade is expanded, we assume that touch handling outside the shades is handled + // by the scrim that appears behind the shades. No need to intercept anything here. + return false + } + + return when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + // Record where the pointer was placed and which pointer it was. + interactionState = + InteractionState( + initialX = event.x, + initialY = event.y, + currentY = event.y, + pointerId = event.getPointerId(0), + isDraggingHorizontally = false, + isDraggingVertically = false, + ) + + false + } + MotionEvent.ACTION_MOVE -> { + interactionState?.let { + val pointerIndex = event.findPointerIndex(it.pointerId) + val currentX = event.getX(pointerIndex) + val currentY = event.getY(pointerIndex) + if (!it.isDraggingHorizontally && !it.isDraggingVertically) { + val xDistanceTravelled = abs(currentX - it.initialX) + val yDistanceTravelled = abs(currentY - it.initialY) + val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop + interactionState = + when { + yDistanceTravelled > touchSlop -> + it.copy(isDraggingVertically = true) + xDistanceTravelled > touchSlop -> + it.copy(isDraggingHorizontally = true) + else -> interactionState + } + } + } + + // We want to intercept the rest of the gesture if we're dragging. + interactionState.isDraggingVertically() + } + MotionEvent.ACTION_UP, + MotionEvent.ACTION_CANCEL -> + // Make sure that we intercept the up or cancel if we're dragging, to handle drag + // end and cancel. + interactionState.isDraggingVertically() + else -> false + } + } + + /** + * Notifies that a [MotionEvent] in a series of events of a gesture that was intercepted due to + * the result of [shouldIntercept] has been received. + * + * @param event The [MotionEvent] to handle. + * @param viewWidthPx The width of the view, in pixels. + * @return `true` if the event was consumed, `false` otherwise. + */ + fun onTouchEvent(event: MotionEvent, viewWidthPx: Int): Boolean { + return when (event.actionMasked) { + MotionEvent.ACTION_MOVE -> { + interactionState?.let { + if (it.isDraggingVertically) { + val pointerIndex = event.findPointerIndex(it.pointerId) + val previousY = it.currentY + val currentY = event.getY(pointerIndex) + interactionState = + it.copy( + currentY = currentY, + ) + + val yDragAmountPx = currentY - previousY + if (yDragAmountPx != 0f) { + interactor.sendProxiedInput( + ProxiedInputModel.OnDrag( + xFraction = event.x / viewWidthPx, + yDragAmountPx = yDragAmountPx, + ) + ) + } + } + } + + true + } + MotionEvent.ACTION_UP -> { + if (interactionState.isDraggingVertically()) { + // We finished dragging. Record that so the multi-shade framework can issue a + // fling, if the velocity reached in the drag was high enough, for example. + interactor.sendProxiedInput(ProxiedInputModel.OnDragEnd) + } + + interactionState = null + true + } + MotionEvent.ACTION_CANCEL -> { + if (interactionState.isDraggingVertically()) { + // Our drag gesture was canceled by the system. This happens primarily in one of + // two occasions: (a) the parent view has decided to intercept the gesture + // itself and/or route it to a different child view or (b) the pointer has + // traveled beyond the bounds of our view and/or the touch display. Either way, + // we pass the cancellation event to the multi-shade framework to record it. + // Doing that allows the multi-shade framework to know that the gesture ended to + // allow new gestures to be accepted. + interactor.sendProxiedInput(ProxiedInputModel.OnDragCancel) + } + + interactionState = null + true + } + else -> false + } + } + + private data class InteractionState( + val initialX: Float, + val initialY: Float, + val currentY: Float, + val pointerId: Int, + val isDraggingHorizontally: Boolean, + val isDraggingVertically: Boolean, + ) + + private fun InteractionState?.isDraggingVertically(): Boolean { + return this?.isDraggingVertically == true + } +} diff --git a/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt new file mode 100644 index 000000000000..c2eaf72a841a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/multishade/shared/math/Math.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.multishade.shared.math + +import androidx.annotation.VisibleForTesting +import kotlin.math.abs + +/** Returns `true` if this [Float] is within [epsilon] of `0`. */ +fun Float.isZero(epsilon: Float = EPSILON): Boolean { + return abs(this) < epsilon +} + +@VisibleForTesting private const val EPSILON = 0.0001f diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt index ce6ab977dea2..ed92c5469d23 100644 --- a/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/viewmodel/MultiShadeViewModel.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -87,10 +86,7 @@ class MultiShadeViewModel( when (shadeConfig) { // In the dual shade configuration, the scrim is enabled when the expansion is // greater than zero on any one of the shades. - is ShadeConfig.DualShadeConfig -> - interactor.maxShadeExpansion - .map { expansion -> expansion > 0 } - .distinctUntilChanged() + is ShadeConfig.DualShadeConfig -> interactor.isAnyShadeExpanded // No scrim in the single shade configuration. is ShadeConfig.SingleShadeConfig -> flowOf(false) } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index e74d78d2b5d9..58ac5b30972f 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -138,7 +138,8 @@ constructor( logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { is NoteTaskLaunchMode.AppBubble -> { - bubbles.showOrHideAppBubble(intent, userTracker.userHandle) + // TODO: provide app bubble icon + bubbles.showOrHideAppBubble(intent, userTracker.userHandle, null /* icon */) // App bubble logging happens on `onBubbleExpandChanged`. logDebug { "onShowNoteTask - opened as app bubble: $info" } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java index 2668d2e36731..fdab9b16c7a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java @@ -91,16 +91,19 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr new QSPanel.OnConfigurationChangedListener() { @Override public void onConfigurationChange(Configuration newConfig) { - mQSLogger.logOnConfigurationChanged( - /* lastOrientation= */ mLastOrientation, - /* newOrientation= */ newConfig.orientation, - /* containerName= */ mView.getDumpableTag()); - - boolean previousSplitShadeState = mShouldUseSplitNotificationShade; + final boolean previousSplitShadeState = mShouldUseSplitNotificationShade; + final int previousOrientation = mLastOrientation; mShouldUseSplitNotificationShade = - LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); + LargeScreenUtils.shouldUseSplitNotificationShade(getResources()); mLastOrientation = newConfig.orientation; + mQSLogger.logOnConfigurationChanged( + /* oldOrientation= */ previousOrientation, + /* newOrientation= */ mLastOrientation, + /* oldShouldUseSplitShade= */ previousSplitShadeState, + /* newShouldUseSplitShade= */ mShouldUseSplitNotificationShade, + /* containerName= */ mView.getDumpableTag()); + switchTileLayoutIfNeeded(); onConfigurationChanged(); if (previousSplitShadeState != mShouldUseSplitNotificationShade) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index 2083cc7b167e..5e4f53181706 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -136,9 +136,8 @@ public class TileServices extends IQSService.Stub { mServices.remove(tile); mTokenMap.remove(service.getToken()); mTiles.remove(tile.getComponent()); - final String slot = tile.getComponent().getClassName(); - // TileServices doesn't know how to add more than 1 icon per slot, so remove all - mMainHandler.post(() -> mStatusBarIconController.removeAllIconsForSlot(slot)); + final String slot = getStatusBarIconSlotName(tile.getComponent()); + mMainHandler.post(() -> mStatusBarIconController.removeIconForTile(slot)); } } @@ -312,12 +311,11 @@ public class TileServices extends IQSService.Stub { ? new StatusBarIcon(userHandle, packageName, icon, 0, 0, contentDescription) : null; + final String slot = getStatusBarIconSlotName(componentName); mMainHandler.post(new Runnable() { @Override public void run() { - StatusBarIconController iconController = mStatusBarIconController; - iconController.setIcon(componentName.getClassName(), statusIcon); - iconController.setExternalIcon(componentName.getClassName()); + mStatusBarIconController.setIconFromTile(slot, statusIcon); } }); } @@ -377,6 +375,12 @@ public class TileServices extends IQSService.Stub { mCommandQueue.removeCallback(mRequestListeningCallback); } + /** Returns the slot name that should be used when adding or removing status bar icons. */ + private String getStatusBarIconSlotName(ComponentName componentName) { + return componentName.getClassName(); + } + + private final CommandQueue.Callbacks mRequestListeningCallback = new CommandQueue.Callbacks() { @Override public void requestTileServiceListeningState(@NonNull ComponentName componentName) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index 23c41db6d5a6..5b461a6d8bad 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -16,8 +16,12 @@ package com.android.systemui.qs.logging +import android.content.res.Configuration.ORIENTATION_LANDSCAPE +import android.content.res.Configuration.ORIENTATION_PORTRAIT +import android.content.res.Configuration.Orientation import android.service.quicksettings.Tile import android.view.View +import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.log.ConstantStringsLogger import com.android.systemui.plugins.log.ConstantStringsLoggerImpl @@ -32,8 +36,12 @@ import javax.inject.Inject private const val TAG = "QSLog" -class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : - ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { +class QSLogger +@Inject +constructor( + @QSLog private val buffer: LogBuffer, + @QSConfigLog private val configChangedBuffer: LogBuffer, +) : ConstantStringsLogger by ConstantStringsLoggerImpl(buffer, TAG) { fun logException(@CompileTimeConstant logMsg: String, ex: Exception) { buffer.log(TAG, ERROR, {}, { logMsg }, exception = ex) @@ -264,19 +272,28 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } fun logOnConfigurationChanged( - lastOrientation: Int, - newOrientation: Int, + @Orientation oldOrientation: Int, + @Orientation newOrientation: Int, + newShouldUseSplitShade: Boolean, + oldShouldUseSplitShade: Boolean, containerName: String ) { - buffer.log( + configChangedBuffer.log( TAG, DEBUG, { str1 = containerName - int1 = lastOrientation + int1 = oldOrientation int2 = newOrientation + bool1 = oldShouldUseSplitShade + bool2 = newShouldUseSplitShade }, - { "configuration change: $str1 orientation was $int1, now $int2" } + { + "config change: " + + "$str1 orientation=${toOrientationString(int2)} " + + "(was ${toOrientationString(int1)}), " + + "splitShade=$bool2 (was $bool1)" + } ) } @@ -353,3 +370,11 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : } } } + +private inline fun toOrientationString(@Orientation orientation: Int): String { + return when (orientation) { + ORIENTATION_LANDSCAPE -> "land" + ORIENTATION_PORTRAIT -> "port" + else -> "undefined" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index 557e95c64443..7ad594ee87ac 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -115,6 +115,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Supplier; @@ -613,11 +615,13 @@ public class ScreenshotController { // Note that this may block if the sound is still being loaded (very unlikely) but we can't // reliably release in the background because the service is being destroyed. try { - MediaPlayer player = mCameraSound.get(); + MediaPlayer player = mCameraSound.get(1, TimeUnit.SECONDS); if (player != null) { player.release(); } - } catch (InterruptedException | ExecutionException e) { + } catch (InterruptedException | ExecutionException | TimeoutException e) { + mCameraSound.cancel(true); + Log.w(TAG, "Error releasing shutter sound", e); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt index 48aa60f69cdb..253f07de7b3c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotDetectionController.kt @@ -17,6 +17,8 @@ package com.android.systemui.screenshot import android.content.pm.PackageManager +import android.content.pm.PackageManager.ComponentInfoFlags +import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS import android.view.Display import android.view.IWindowManager import android.view.ViewGroup @@ -45,7 +47,7 @@ constructor( // Convert component names to app names. return components.map { packageManager - .getActivityInfo(it, PackageManager.ComponentInfoFlags.of(0)) + .getActivityInfo(it, ComponentInfoFlags.of(MATCH_DISABLED_COMPONENTS.toLong())) .loadLabel(packageManager) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 1c3e0112967e..3360511617ba 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -159,6 +159,7 @@ import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarController; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; +import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.FalsingManager.FalsingTapListener; import com.android.systemui.plugins.qs.QS; @@ -1592,10 +1593,9 @@ public final class NotificationPanelViewController implements Dumpable { transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - boolean customClockAnimation = - mKeyguardStatusViewController.getClockAnimations() != null - && mKeyguardStatusViewController.getClockAnimations() - .getHasCustomPositionUpdatedAnimation(); + ClockAnimations clockAnims = mKeyguardStatusViewController.getClockAnimations(); + boolean customClockAnimation = clockAnims != null + && clockAnims.getHasCustomPositionUpdatedAnimation(); if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) { // Find the clock, so we can exclude it from this transition. @@ -2897,15 +2897,7 @@ public final class NotificationPanelViewController implements Dumpable { mHeadsUpManager.addListener(mOnHeadsUpChangedListener); mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScrollLayoutController.getHeadsUpCallback(), - NotificationPanelViewController.this); - } - - public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { - if (pickedChild != null) { - updateTrackingHeadsUp(pickedChild); - mExpandingFromHeadsUp = true; - } - // otherwise we update the state when the expansion is finished + new HeadsUpNotificationViewControllerImpl()); } private void onClosingFinished() { @@ -2953,7 +2945,8 @@ public final class NotificationPanelViewController implements Dumpable { } /** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */ - public void setHeadsUpDraggingStartingHeight(int startHeight) { + @VisibleForTesting + void setHeadsUpDraggingStartingHeight(int startHeight) { mHeadsUpStartHeight = startHeight; float scrimMinFraction; if (mSplitShadeEnabled) { @@ -2987,10 +2980,6 @@ public final class NotificationPanelViewController implements Dumpable { mScrimController.setPanelScrimMinFraction(mMinFraction); } - public void clearNotificationEffects() { - mCentralSurfaces.clearNotificationEffects(); - } - private boolean isPanelVisibleBecauseOfHeadsUp() { return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway) && mBarState == StatusBarState.SHADE; @@ -3629,7 +3618,13 @@ public final class NotificationPanelViewController implements Dumpable { : (mKeyguardStateController.canDismissLockScreen() ? UNLOCK : BOUNCER_UNLOCK); - fling(vel, expand, isFalseTouch(x, y, interactionType)); + // don't fling while in keyguard to avoid jump in shade expand animation; + // touch has been intercepted already so flinging here is redundant + if (mBarState == KEYGUARD && mExpandedFraction >= 1.0) { + mShadeLog.d("NPVC endMotionEvent - skipping fling on keyguard"); + } else { + fling(vel, expand, isFalseTouch(x, y, interactionType)); + } onTrackingStopped(expand); mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown; if (mUpdateFlingOnLayout) { @@ -5112,17 +5107,26 @@ public final class NotificationPanelViewController implements Dumpable { captureValues(transitionValues); } + @Nullable @Override - public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, - TransitionValues endValues) { + public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues, + @Nullable TransitionValues endValues) { + if (startValues == null || endValues == null) { + return null; + } ValueAnimator anim = ValueAnimator.ofFloat(0, 1); Rect from = (Rect) startValues.values.get(PROP_BOUNDS); Rect to = (Rect) endValues.values.get(PROP_BOUNDS); - anim.addUpdateListener( - animation -> mController.getClockAnimations().onPositionUpdated( - from, to, animation.getAnimatedFraction())); + anim.addUpdateListener(animation -> { + ClockAnimations clockAnims = mController.getClockAnimations(); + if (clockAnims == null) { + return; + } + + clockAnims.onPositionUpdated(from, to, animation.getAnimatedFraction()); + }); return anim; } @@ -5133,6 +5137,33 @@ public final class NotificationPanelViewController implements Dumpable { } } + private final class HeadsUpNotificationViewControllerImpl implements + HeadsUpTouchHelper.HeadsUpNotificationViewController { + @Override + public void setHeadsUpDraggingStartingHeight(int startHeight) { + NotificationPanelViewController.this.setHeadsUpDraggingStartingHeight(startHeight); + } + + @Override + public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { + if (pickedChild != null) { + updateTrackingHeadsUp(pickedChild); + mExpandingFromHeadsUp = true; + } + // otherwise we update the state when the expansion is finished + } + + @Override + public void startExpand(float x, float y, boolean startTracking, float expandedHeight) { + startExpandMotion(x, y, startTracking, expandedHeight); + } + + @Override + public void clearNotificationEffects() { + mCentralSurfaces.clearNotificationEffects(); + } + } + private final class ShadeAccessibilityDelegate extends AccessibilityDelegate { @Override public void onInitializeAccessibilityNodeInfo(View host, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 5f6f158277d7..0318fa570a78 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -32,6 +32,8 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; @@ -50,6 +52,7 @@ import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor; +import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor; import com.android.systemui.multishade.ui.view.MultiShadeView; import com.android.systemui.statusbar.DragDownHelper; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -118,6 +121,7 @@ public class NotificationShadeWindowViewController { step.getTransitionState() == TransitionState.RUNNING; }; private final SystemClock mClock; + private final @Nullable MultiShadeMotionEventInteractor mMultiShadeMotionEventInteractor; @Inject public NotificationShadeWindowViewController( @@ -145,7 +149,8 @@ public class NotificationShadeWindowViewController { PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, FeatureFlags featureFlags, Provider<MultiShadeInteractor> multiShadeInteractorProvider, - SystemClock clock) { + SystemClock clock, + Provider<MultiShadeMotionEventInteractor> multiShadeMotionEventInteractorProvider) { mLockscreenShadeTransitionController = transitionController; mFalsingCollector = falsingCollector; mStatusBarStateController = statusBarStateController; @@ -180,22 +185,18 @@ public class NotificationShadeWindowViewController { mClock = clock; if (ComposeFacade.INSTANCE.isComposeAvailable() && featureFlags.isEnabled(Flags.DUAL_SHADE)) { + mMultiShadeMotionEventInteractor = multiShadeMotionEventInteractorProvider.get(); final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub); if (multiShadeViewStub != null) { final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate(); multiShadeView.init(multiShadeInteractorProvider.get(), clock); } + } else { + mMultiShadeMotionEventInteractor = null; } } /** - * @return Location where to place the KeyguardBouncer - */ - public ViewGroup getBouncerContainer() { - return mView.findViewById(R.id.keyguard_bouncer_container); - } - - /** * @return Location where to place the KeyguardMessageArea */ public AuthKeyguardMessageArea getKeyguardMessageArea() { @@ -349,16 +350,17 @@ public class NotificationShadeWindowViewController { return true; } - boolean intercept = false; - if (mNotificationPanelViewController.isFullyExpanded() + if (mMultiShadeMotionEventInteractor != null) { + // This interactor is not null only if the dual shade feature is enabled. + return mMultiShadeMotionEventInteractor.shouldIntercept(ev); + } else if (mNotificationPanelViewController.isFullyExpanded() && mDragDownHelper.isDragDownEnabled() && !mService.isBouncerShowing() && !mStatusBarStateController.isDozing()) { - intercept = mDragDownHelper.onInterceptTouchEvent(ev); + return mDragDownHelper.onInterceptTouchEvent(ev); + } else { + return false; } - - return intercept; - } @Override @@ -381,13 +383,20 @@ public class NotificationShadeWindowViewController { return true; } - if ((mDragDownHelper.isDragDownEnabled() && !handled) + if (handled) { + return true; + } + + if (mMultiShadeMotionEventInteractor != null) { + // This interactor is not null only if the dual shade feature is enabled. + return mMultiShadeMotionEventInteractor.onTouchEvent(ev, mView.getWidth()); + } else if (mDragDownHelper.isDragDownEnabled() || mDragDownHelper.isDraggingDown()) { // we still want to finish our drag down gesture when locking the screen - handled = mDragDownHelper.onTouchEvent(ev); + return mDragDownHelper.onTouchEvent(ev); + } else { + return false; } - - return handled; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java index 5ba8801e0f63..bfb6416ac78a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java @@ -94,7 +94,11 @@ public interface NotificationInterruptStateProvider { /** * No conditions blocking FSI launch. */ - FSI_EXPECTED_NOT_TO_HUN(true); + FSI_EXPECTED_NOT_TO_HUN(true), + /** + * The notification is coming from a suspended packages, so FSI is suppressed. + */ + NO_FSI_SUSPENDED(false); public final boolean shouldLaunch; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 6f4eed3c1612..4aaa7ca61d34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -28,7 +28,6 @@ import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; -import android.os.SystemProperties; import android.provider.Settings; import android.service.notification.StatusBarNotification; @@ -274,6 +273,12 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter suppressedByDND); } + // Notification is coming from a suspended package, block FSI + if (entry.getRanking().isSuspended()) { + return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED, + suppressedByDND); + } + // If the screen is off, then launch the FullScreenIntent if (!mPowerManager.isInteractive()) { return getDecisionGivenSuppression(FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 55fa47951fe1..7f8c1351aa7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -217,8 +217,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn NotificationPanelViewController getNotificationPanelViewController(); - ViewGroup getBouncerContainer(); - /** Get the Keyguard Message Area that displays auth messages. */ AuthKeyguardMessageArea getKeyguardMessageArea(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 5e3c1c59666d..2f404873dc7a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1722,11 +1722,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public ViewGroup getBouncerContainer() { - return mNotificationShadeWindowViewController.getBouncerContainer(); - } - - @Override public AuthKeyguardMessageArea getKeyguardMessageArea() { return mNotificationShadeWindowViewController.getKeyguardMessageArea(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java index 90d0b697337a..16c2e36af6b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java @@ -21,7 +21,6 @@ import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.systemui.Gefingerpoken; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; @@ -31,21 +30,21 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; */ public class HeadsUpTouchHelper implements Gefingerpoken { - private HeadsUpManagerPhone mHeadsUpManager; - private Callback mCallback; + private final HeadsUpManagerPhone mHeadsUpManager; + private final Callback mCallback; private int mTrackingPointer; - private float mTouchSlop; + private final float mTouchSlop; private float mInitialTouchX; private float mInitialTouchY; private boolean mTouchingHeadsUpView; private boolean mTrackingHeadsUp; private boolean mCollapseSnoozes; - private NotificationPanelViewController mPanel; + private final HeadsUpNotificationViewController mPanel; private ExpandableNotificationRow mPickedChild; public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, Callback callback, - NotificationPanelViewController notificationPanelView) { + HeadsUpNotificationViewController notificationPanelView) { mHeadsUpManager = headsUpManager; mCallback = callback; mPanel = notificationPanelView; @@ -116,7 +115,7 @@ public class HeadsUpTouchHelper implements Gefingerpoken { int startHeight = (int) (mPickedChild.getActualHeight() + mPickedChild.getTranslationY()); mPanel.setHeadsUpDraggingStartingHeight(startHeight); - mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight); + mPanel.startExpand(x, y, true /* startTracking */, startHeight); // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. mHeadsUpManager.unpinAll(true); @@ -181,4 +180,19 @@ public class HeadsUpTouchHelper implements Gefingerpoken { boolean isExpanded(); Context getContext(); } + + /** The controller for a view that houses heads up notifications. */ + public interface HeadsUpNotificationViewController { + /** Called when a HUN is dragged to indicate the starting height for shade motion. */ + void setHeadsUpDraggingStartingHeight(int startHeight); + + /** Sets notification that is being expanded. */ + void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow); + + /** Called when a MotionEvent is about to trigger expansion. */ + void startExpand(float newX, float newY, boolean startTracking, float expandedHeight); + + /** Clear any effects that were added for the expansion. */ + void clearNotificationEffects(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt index 9a5d1b5514f5..62d302f2a592 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt @@ -26,6 +26,8 @@ import android.view.ViewGroup import android.view.ViewTreeObserver import com.android.systemui.Gefingerpoken import com.android.systemui.R +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.shade.ShadeController import com.android.systemui.shade.ShadeLogger import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator @@ -215,6 +217,7 @@ class PhoneStatusBarViewController private constructor( private val unfoldComponent: Optional<SysUIUnfoldComponent>, @Named(UNFOLD_STATUS_BAR) private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>, + private val featureFlags: FeatureFlags, private val userChipViewModel: StatusBarUserChipViewModel, private val centralSurfaces: CentralSurfaces, private val shadeController: ShadeController, @@ -224,17 +227,25 @@ class PhoneStatusBarViewController private constructor( ) { fun create( view: PhoneStatusBarView - ) = - PhoneStatusBarViewController( - view, - progressProvider.getOrNull(), - centralSurfaces, - shadeController, - shadeLogger, - unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(), - userChipViewModel, - viewUtil, - configurationController + ): PhoneStatusBarViewController { + val statusBarMoveFromCenterAnimationController = + if (featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) { + unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController() + } else { + null + } + + return PhoneStatusBarViewController( + view, + progressProvider.getOrNull(), + centralSurfaces, + shadeController, + shadeLogger, + statusBarMoveFromCenterAnimationController, + userChipViewModel, + viewUtil, + configurationController ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java index 04cc8ce792d1..30d2295206d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java @@ -81,28 +81,22 @@ public interface StatusBarIconController { void refreshIconGroup(IconManager iconManager); /** - * Adds or updates an icon for a given slot for a **tile service icon**. + * Adds or updates an icon that comes from an active tile service. * - * TODO(b/265307726): Merge with {@link #setIcon(String, StatusBarIcon)} or make this method - * much more clearly distinct from that method. + * If the icon is null, the icon will be removed. */ - void setExternalIcon(String slot); + void setIconFromTile(String slot, @Nullable StatusBarIcon icon); + + /** Removes an icon that had come from an active tile service. */ + void removeIconForTile(String slot); /** * Adds or updates an icon for the given slot for **internal system icons**. * - * TODO(b/265307726): Rename to `setInternalIcon`, or merge this appropriately with the - * {@link #setIcon(String, StatusBarIcon)} method. + * TODO(b/265307726): Re-name this to `setInternalIcon`. */ void setIcon(String slot, int resourceId, CharSequence contentDescription); - /** - * Adds or updates an icon for the given slot for an **externally-provided icon**. - * - * TODO(b/265307726): Rename to `setExternalIcon` or something similar. - */ - void setIcon(String slot, StatusBarIcon icon); - /** */ void setWifiIcon(String slot, WifiIconState state); @@ -152,15 +146,10 @@ public interface StatusBarIconController { */ void removeIcon(String slot, int tag); - /** */ - void removeAllIconsForSlot(String slot); - /** - * Removes all the icons for the given slot. - * - * Only use this for icons that have come from **an external process**. + * TODO(b/265307726): Re-name this to `removeAllIconsForInternalSlot`. */ - void removeAllIconsForExternalSlot(String slot); + void removeAllIconsForSlot(String slot); // TODO: See if we can rename this tunable name. String ICON_HIDE_LIST = "icon_blacklist"; @@ -618,13 +607,6 @@ public interface StatusBarIconController { mGroup.removeAllViews(); } - protected void onIconExternal(int viewIndex, int height) { - ImageView imageView = (ImageView) mGroup.getChildAt(viewIndex); - imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); - imageView.setAdjustViewBounds(true); - setHeightAndCenter(imageView, height); - } - protected void onDensityOrFontScaleChanged() { for (int i = 0; i < mGroup.getChildCount(); i++) { View child = mGroup.getChildAt(i); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java index 0727c5af0dc7..3a184239ac43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java @@ -32,7 +32,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.Dumpable; -import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; @@ -62,7 +61,7 @@ import javax.inject.Inject; */ @SysUISingleton public class StatusBarIconControllerImpl implements Tunable, - ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode { + ConfigurationListener, Dumpable, StatusBarIconController, DemoMode { private static final String TAG = "StatusBarIconController"; // Use this suffix to prevent external icon slot names from unintentionally overriding our @@ -93,7 +92,7 @@ public class StatusBarIconControllerImpl implements Tunable, mStatusBarPipelineFlags = statusBarPipelineFlags; configurationController.addCallback(this); - commandQueue.addCallback(this); + commandQueue.addCallback(mCommandQueueCallbacks); tunerService.addTunable(this, ICON_HIDE_LIST); demoModeController.addCallback(this); dumpManager.registerDumpable(getClass().getSimpleName(), this); @@ -350,26 +349,35 @@ public class StatusBarIconControllerImpl implements Tunable, } } + private final CommandQueue.Callbacks mCommandQueueCallbacks = new CommandQueue.Callbacks() { + @Override + public void setIcon(String slot, StatusBarIcon icon) { + // Icons that come from CommandQueue are from external services. + setExternalIcon(slot, icon); + } + + @Override + public void removeIcon(String slot) { + removeAllIconsForExternalSlot(slot); + } + }; + @Override - public void setExternalIcon(String slot) { - String slotName = createExternalSlotName(slot); - int viewIndex = mStatusBarIconList.getViewIndex(slotName, 0); - int height = mContext.getResources().getDimensionPixelSize( - R.dimen.status_bar_icon_drawing_size); - mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height)); + public void setIconFromTile(String slot, StatusBarIcon icon) { + setExternalIcon(slot, icon); } - // Override for *both* CommandQueue.Callbacks AND StatusBarIconController. - // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to - // differentiate between those callback methods and StatusBarIconController methods. @Override - public void setIcon(String slot, StatusBarIcon icon) { - String slotName = createExternalSlotName(slot); + public void removeIconForTile(String slot) { + removeAllIconsForExternalSlot(slot); + } + + private void setExternalIcon(String slot, StatusBarIcon icon) { if (icon == null) { - removeAllIconsForSlot(slotName); + removeAllIconsForExternalSlot(slot); return; } - + String slotName = createExternalSlotName(slot); StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon); setIcon(slotName, holder); } @@ -417,14 +425,6 @@ public class StatusBarIconControllerImpl implements Tunable, } } - // CommandQueue.Callbacks override - // TODO(b/265307726): Pull out the CommandQueue callbacks into a member variable to - // differentiate between those callback methods and StatusBarIconController methods. - @Override - public void removeIcon(String slot) { - removeAllIconsForExternalSlot(slot); - } - /** */ @Override public void removeIcon(String slot, int tag) { @@ -444,8 +444,7 @@ public class StatusBarIconControllerImpl implements Tunable, mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex)); } - @Override - public void removeAllIconsForExternalSlot(String slotName) { + private void removeAllIconsForExternalSlot(String slotName) { removeAllIconsForSlot(createExternalSlotName(slotName)); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 06d0758c90eb..f06b5db84588 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -381,7 +381,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mCentralSurfaces = centralSurfaces; mBiometricUnlockController = biometricUnlockController; - ViewGroup container = mCentralSurfaces.getBouncerContainer(); mPrimaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); mNotificationPanelViewController = notificationPanelViewController; if (shadeExpansionStateManager != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index ed978c39eb3d..24ddded8847a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -453,9 +453,6 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue protected int adjustDisableFlags(int state) { boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); - if (headsUpVisible) { - state |= DISABLE_CLOCK; - } if (!mKeyguardStateController.isLaunchTransitionFadingAway() && !mKeyguardStateController.isKeyguardFadingAway() @@ -473,6 +470,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue state |= DISABLE_ONGOING_CALL_CHIP; } + if (headsUpVisible) { + // Disable everything on the left side of the status bar, since the app name for the + // heads up notification appears there instead. + state |= DISABLE_CLOCK; + state |= DISABLE_ONGOING_CALL_CHIP; + } + return state; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index 4156fc152602..73bf188857c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -179,6 +179,10 @@ constructor( fun logDefaultMobileIconGroup(group: SignalIcon.MobileIconGroup) { buffer.log(TAG, LogLevel.INFO, { str1 = group.name }, { "defaultMobileIconGroup: $str1" }) } + + fun logOnSubscriptionsChanged() { + buffer.log(TAG, LogLevel.INFO, {}, { "onSubscriptionsChanged" }) + } } private const val TAG = "MobileInputLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index b7da3f27c70a..991b7868439a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -129,6 +129,7 @@ constructor( val callback = object : SubscriptionManager.OnSubscriptionsChangedListener() { override fun onSubscriptionsChanged() { + logger.logOnSubscriptionsChanged() trySend(Unit) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 805368cc1f0a..f1269f2b012a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -398,6 +398,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled); pw.println(" isKeyguardFadingAway: " + isKeyguardFadingAway()); pw.println(" isKeyguardGoingAway: " + isKeyguardGoingAway()); + pw.println(" isLaunchTransitionFadingAway: " + isLaunchTransitionFadingAway()); } private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback { diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt index 9952cfd4e85b..3805019d597f 100644 --- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt +++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt @@ -261,22 +261,26 @@ constructor( private fun trackAndLogUsiSession(deviceId: Int, batteryStateValid: Boolean) { // TODO(b/268618918) handle cases where an invalid battery callback from a previous stylus // is sent after the actual valid callback + val hasBtConnection = if (inputDeviceBtSessionIdMap.isEmpty()) 0 else 1 + if (batteryStateValid && usiSessionId == null) { logDebug { "USI battery newly present, entering new USI session: $deviceId" } usiSessionId = instanceIdSequence.newInstanceId() - uiEventLogger.logWithInstanceId( + uiEventLogger.logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, 0, null, - usiSessionId + usiSessionId, + hasBtConnection, ) } else if (!batteryStateValid && usiSessionId != null) { logDebug { "USI battery newly absent, exiting USI session: $deviceId" } - uiEventLogger.logWithInstanceId( + uiEventLogger.logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED, 0, null, - usiSessionId + usiSessionId, + hasBtConnection, ) usiSessionId = null } diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 125cc761d400..6e58f2296c86 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -18,7 +18,8 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect import android.view.View -import androidx.annotation.AttrRes +import androidx.annotation.ColorRes +import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon import com.android.systemui.temporarydisplay.TemporaryViewInfo @@ -48,7 +49,7 @@ data class ChipbarInfo( override val priority: ViewPriority, ) : TemporaryViewInfo() { companion object { - @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary + @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color } } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt index 6ef828fbdfdb..9d8c4a5cf8b0 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt @@ -87,6 +87,7 @@ constructor( private var isFolded: Boolean = false private var isUnfoldHandled: Boolean = true private var overlayAddReason: AddOverlayReason? = null + private var isTouchBlocked: Boolean = true private var currentRotation: Int = context.display!!.rotation @@ -254,7 +255,15 @@ constructor( params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS params.fitInsetsTypes = 0 - params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + + val touchFlags = + if (isTouchBlocked) { + // Touchable by default, so it will block the touches + 0 + } else { + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + } + params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or touchFlags params.setTrustedOverlay() val packageName: String = context.opPackageName @@ -263,6 +272,24 @@ constructor( return params } + private fun updateTouchBlockIfNeeded(progress: Float) { + // When unfolding unblock touches a bit earlier than the animation end as the + // interpolation has a long tail of very slight movement at the end which should not + // affect much the usage of the device + val shouldBlockTouches = + if (overlayAddReason == UNFOLD) { + progress < UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS + } else { + true + } + + if (isTouchBlocked != shouldBlockTouches) { + isTouchBlocked = shouldBlockTouches + + traceSection("$TAG#relayoutToUpdateTouch") { root?.relayout(getLayoutParams()) } + } + } + private fun createLightRevealEffect(): LightRevealEffect { val isVerticalFold = currentRotation == Surface.ROTATION_0 || currentRotation == Surface.ROTATION_180 @@ -289,7 +316,10 @@ constructor( private inner class TransitionListener : TransitionProgressListener { override fun onTransitionProgress(progress: Float) { - executeInBackground { scrimView?.revealAmount = calculateRevealAmount(progress) } + executeInBackground { + scrimView?.revealAmount = calculateRevealAmount(progress) + updateTouchBlockIfNeeded(progress) + } } override fun onTransitionFinished() { @@ -361,5 +391,7 @@ constructor( // constants for revealAmount. const val TRANSPARENT = 1f const val BLACK = 0f + + private const val UNFOLD_BLOCK_TOUCHES_UNTIL_PROGRESS = 0.8f } } diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java index 460b7d9f2de9..a5828c7704c3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java +++ b/packages/SystemUI/src/com/android/systemui/util/sensors/PostureDependentProximitySensor.java @@ -23,6 +23,8 @@ import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.Execution; +import java.util.HashSet; + import javax.inject.Inject; /** @@ -37,6 +39,7 @@ class PostureDependentProximitySensor extends ProximitySensorImpl { private final ThresholdSensor[] mPostureToPrimaryProxSensorMap; private final ThresholdSensor[] mPostureToSecondaryProxSensorMap; + private final HashSet<Listener> mListenersRegisteredWhenProxUnavailable = new HashSet<>(); private final DevicePostureController mDevicePostureController; @Inject @@ -69,6 +72,25 @@ class PostureDependentProximitySensor extends ProximitySensorImpl { mDevicePostureController.removeCallback(mDevicePostureCallback); } + @Override + public void register(ThresholdSensor.Listener listener) { + if (!isLoaded()) { + logDebug("No prox sensor when registering listener=" + listener); + mListenersRegisteredWhenProxUnavailable.add(listener); + } + + super.register(listener); + } + + @Override + public void unregister(ThresholdSensor.Listener listener) { + if (mListenersRegisteredWhenProxUnavailable.remove(listener)) { + logDebug("Removing listener from mListenersRegisteredWhenProxUnavailable " + + listener); + } + super.unregister(listener); + } + private void chooseSensors() { if (mDevicePosture >= mPostureToPrimaryProxSensorMap.length || mDevicePosture >= mPostureToSecondaryProxSensorMap.length) { @@ -98,6 +120,14 @@ class PostureDependentProximitySensor extends ProximitySensorImpl { mInitializedListeners = false; registerInternal(); + + final Listener[] listenersToReregister = + mListenersRegisteredWhenProxUnavailable.toArray(new Listener[0]); + mListenersRegisteredWhenProxUnavailable.clear(); + for (Listener listener : listenersToReregister) { + logDebug("Re-register listener " + listener); + register(listener); + } } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index a5f90f8441b2..b15ac39dc57d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; @@ -365,6 +366,12 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { assertEquals(View.VISIBLE, mFakeWeatherView.getVisibility()); } + @Test + public void testGetClockAnimations_nullClock_returnsNull() { + when(mClockEventController.getClock()).thenReturn(null); + assertNull(mController.getClockAnimations()); + } + private void verifyAttachment(VerificationMode times) { verify(mClockRegistry, times).registerClockChangeListener( any(ClockRegistry.ClockChangeListener.class)); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index f966eb33fc14..b73330fb09c8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -196,6 +196,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { .thenReturn(mKeyguardMessageAreaController); when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController); when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); mKeyguardPasswordViewController = new KeyguardPasswordViewController( (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, SecurityMode.Password, mLockPatternUtils, null, @@ -554,6 +555,22 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void testSecurityCallbackFinish() { + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); + when(mKeyguardUpdateMonitor.isUserUnlocked(0)).thenReturn(true); + mKeyguardSecurityContainerController.finish(true, 0); + verify(mViewMediatorCallback).keyguardDone(anyBoolean(), anyInt()); + } + + @Test + public void testSecurityCallbackFinish_cannotDismissLockScreenAndNotStrongAuth() { + when(mFeatureFlags.isEnabled(Flags.PREVENT_BYPASS_KEYGUARD)).thenReturn(true); + when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); + mKeyguardSecurityContainerController.finish(false, 0); + verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); + } + + @Test public void testOnStartingToHide() { mKeyguardSecurityContainerController.onStartingToHide(); verify(mInputViewController).onStartingToHide(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 86ba30cee7a3..fb21db796c3b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -2256,6 +2256,26 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void assistantVisible_requestActiveUnlock() { + // GIVEN active unlock requests from the assistant are allowed + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.ASSISTANT)).thenReturn(true); + + // GIVEN should trigger active unlock + keyguardIsVisible(); + keyguardNotGoingAway(); + statusBarShadeIsNotLocked(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // WHEN the assistant is visible + mKeyguardUpdateMonitor.setAssistantVisible(true); + + // THEN request unlock with keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + @Test public void fingerprintFailure_requestActiveUnlock_dismissKeyguard() throws RemoteException { // GIVEN shouldTriggerActiveUnlock @@ -2489,6 +2509,57 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void unfoldFromPostureChange_requestActiveUnlock_forceDismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock + keyguardIsVisible(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on wakeup + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) + .thenReturn(true); + + // GIVEN an unfold should force dismiss the keyguard + when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard( + PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(true); + + // WHEN device posture changes to unfold + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + + // THEN request unlock with a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(true)); + } + + + @Test + public void unfoldFromPostureChange_requestActiveUnlock_noDismissKeyguard() + throws RemoteException { + // GIVEN shouldTriggerActiveUnlock on wake from UNFOLD_DEVICE + keyguardIsVisible(); + when(mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())).thenReturn(true); + + // GIVEN active unlock triggers on wakeup + when(mActiveUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE)) + .thenReturn(true); + + // GIVEN an unfold should NOT force dismiss the keyguard + when(mActiveUnlockConfig.shouldWakeupForceDismissKeyguard( + PowerManager.WAKE_REASON_UNFOLD_DEVICE)).thenReturn(false); + + // WHEN device posture changes to unfold + deviceInPostureStateOpened(); + mTestableLooper.processAllMessages(); + + // THEN request unlock WITHOUT a keyguard dismissal + verify(mTrustManager).reportUserRequestedUnlock(eq(KeyguardUpdateMonitor.getCurrentUser()), + eq(false)); + } + + @Test public void detectFingerprint_onTemporaryLockoutReset_authenticateFingerprint() { ArgumentCaptor<FingerprintManager.LockoutResetCallback> fpLockoutResetCallbackCaptor = ArgumentCaptor.forClass(FingerprintManager.LockoutResetCallback.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index 21191db3e8b9..0ab675cd6873 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -62,6 +62,7 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.recents.OverviewProxyService import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor @@ -98,7 +99,6 @@ class SideFpsControllerTest : SysuiTestCase() { @JvmField @Rule var rule = MockitoJUnit.rule() - @Mock lateinit var keyguardStateController: KeyguardStateController @Mock lateinit var layoutInflater: LayoutInflater @Mock lateinit var fingerprintManager: FingerprintManager @Mock lateinit var windowManager: WindowManager @@ -138,7 +138,8 @@ class SideFpsControllerTest : SysuiTestCase() { keyguardBouncerRepository = FakeKeyguardBouncerRepository() alternateBouncerInteractor = AlternateBouncerInteractor( - keyguardStateController, + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), keyguardBouncerRepository, FakeBiometricSettingsRepository(), FakeDeviceEntryFingerprintAuthRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 786cb01621bb..cefa9b129262 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -35,6 +35,7 @@ import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInt import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -93,6 +94,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle ) mAlternateBouncerInteractor = AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), mock(KeyguardStateController::class.java), keyguardBouncerRepository, mock(BiometricSettingsRepository::class.java), diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt index 10757ae4ad99..5b3e51828681 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt @@ -201,6 +201,56 @@ class ControlsUiControllerImplTest : SysuiTestCase() { } @Test + fun testSingleAppHeaderIsNotClickable() { + mockLayoutInflater() + val packageName = "pkg" + `when`(authorizedPanelsRepository.getAuthorizedPanels()).thenReturn(setOf(packageName)) + val panel = SelectedItem.PanelItem("App name", ComponentName(packageName, "cls")) + val serviceInfo = setUpPanel(panel) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + + captor.value.onServicesUpdated(listOf(serviceInfo)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val header: View = parent.requireViewById(R.id.controls_header) + assertThat(header.isClickable).isFalse() + assertThat(header.hasOnClickListeners()).isFalse() + } + + @Test + fun testMultipleAppHeaderIsClickable() { + mockLayoutInflater() + val packageName1 = "pkg" + val panel1 = SelectedItem.PanelItem("App name 1", ComponentName(packageName1, "cls")) + val serviceInfo1 = setUpPanel(panel1) + + val packageName2 = "pkg" + val panel2 = SelectedItem.PanelItem("App name 2", ComponentName(packageName2, "cls")) + val serviceInfo2 = setUpPanel(panel2) + + `when`(authorizedPanelsRepository.getAuthorizedPanels()) + .thenReturn(setOf(packageName1, packageName2)) + + underTest.show(parent, {}, context) + + val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>() + + verify(controlsListingController).addCallback(capture(captor)) + + captor.value.onServicesUpdated(listOf(serviceInfo1, serviceInfo2)) + FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor) + + val header: View = parent.requireViewById(R.id.controls_header) + assertThat(header.isClickable).isTrue() + assertThat(header.hasOnClickListeners()).isTrue() + } + + @Test fun testPanelControllerStartActivityWithCorrectArguments() { mockLayoutInflater() val packageName = "pkg" diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java index 19347c768524..58eb7d4f3ea7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/conditions/DreamConditionTest.java @@ -21,9 +21,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.DreamManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -50,6 +52,9 @@ public class DreamConditionTest extends SysuiTestCase { @Mock Condition.Callback mCallback; + @Mock + DreamManager mDreamManager; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -59,29 +64,39 @@ public class DreamConditionTest extends SysuiTestCase { * Ensure a dreaming state immediately triggers the condition. */ @Test - public void testInitialState() { - final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); - when(mContext.registerReceiver(any(), any())).thenReturn(intent); - final DreamCondition condition = new DreamCondition(mContext); + public void testInitialDreamingState() { + when(mDreamManager.isDreaming()).thenReturn(true); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); condition.addCallback(mCallback); - condition.start(); verify(mCallback).onConditionChanged(eq(condition)); assertThat(condition.isConditionMet()).isTrue(); } /** + * Ensure a non-dreaming state does not trigger the condition. + */ + @Test + public void testInitialNonDreamingState() { + when(mDreamManager.isDreaming()).thenReturn(false); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); + condition.addCallback(mCallback); + + verify(mCallback, never()).onConditionChanged(eq(condition)); + assertThat(condition.isConditionMet()).isFalse(); + } + + /** * Ensure that changing dream state triggers condition. */ @Test public void testChange() { - final Intent intent = new Intent(Intent.ACTION_DREAMING_STARTED); final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); - when(mContext.registerReceiver(receiverCaptor.capture(), any())).thenReturn(intent); - final DreamCondition condition = new DreamCondition(mContext); + when(mDreamManager.isDreaming()).thenReturn(true); + final DreamCondition condition = new DreamCondition(mContext, mDreamManager); condition.addCallback(mCallback); - condition.start(); + verify(mContext).registerReceiver(receiverCaptor.capture(), any()); clearInvocations(mCallback); receiverCaptor.getValue().onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STOPPED)); verify(mCallback).onConditionChanged(eq(condition)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt index de0e5113571f..2db4596c80a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/RestartDozeListenerTest.kt @@ -20,6 +20,7 @@ import android.os.PowerManager import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -41,13 +42,14 @@ class RestartDozeListenerTest : SysuiTestCase() { @Mock lateinit var statusBarStateController: StatusBarStateController @Mock lateinit var powerManager: PowerManager val clock = FakeSystemClock() + val executor = FakeExecutor(clock) lateinit var listener: StatusBarStateController.StateListener @Before fun setup() { MockitoAnnotations.initMocks(this) restartDozeListener = - RestartDozeListener(settings, statusBarStateController, powerManager, clock) + RestartDozeListener(settings, statusBarStateController, powerManager, clock, executor) val captor = ArgumentCaptor.forClass(StatusBarStateController.StateListener::class.java) restartDozeListener.init() @@ -58,12 +60,14 @@ class RestartDozeListenerTest : SysuiTestCase() { @Test fun testStoreDreamState_onDreamingStarted() { listener.onDreamingChanged(true) + executor.runAllReady() assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isTrue() } @Test fun testStoreDreamState_onDreamingStopped() { listener.onDreamingChanged(false) + executor.runAllReady() assertThat(settings.getBool(RestartDozeListener.RESTART_NAP_KEY)).isFalse() } @@ -71,6 +75,8 @@ class RestartDozeListenerTest : SysuiTestCase() { fun testRestoreDreamState_dreamingShouldStart() { settings.putBool(RestartDozeListener.RESTART_NAP_KEY, true) restartDozeListener.maybeRestartSleep() + executor.advanceClockToLast() + executor.runAllReady() verify(powerManager).wakeUp(clock.uptimeMillis()) verify(powerManager).goToSleep(clock.uptimeMillis()) } @@ -79,6 +85,8 @@ class RestartDozeListenerTest : SysuiTestCase() { fun testRestoreDreamState_dreamingShouldNot() { settings.putBool(RestartDozeListener.RESTART_NAP_KEY, false) restartDozeListener.maybeRestartSleep() + executor.advanceClockToLast() + executor.runAllReady() verify(powerManager, never()).wakeUp(anyLong()) verify(powerManager, never()).goToSleep(anyLong()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 1365132d6dac..86246f7af033 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintA import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock @@ -39,8 +40,10 @@ 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.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -52,6 +55,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var deviceEntryFingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @@ -73,6 +77,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) } underTest = AlternateBouncerInteractor( + statusBarStateController, keyguardStateController, bouncerRepository, biometricSettingsRepository, @@ -130,6 +135,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test + fun canShowAlternateBouncerForFingerprint_isDozing() { + givenCanShowAlternateBouncer() + whenever(statusBarStateController.isDozing).thenReturn(true) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + + @Test fun show_whenCanShow() { givenCanShowAlternateBouncer() @@ -169,6 +182,42 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(bouncerRepository.alternateBouncerVisible.value) } + @Test + fun onUnlockedIsFalse_doesNotHide() { + // GIVEN alternate bouncer is showing + bouncerRepository.setAlternateVisible(true) + + val keyguardStateControllerCallbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture()) + + // WHEN isUnlocked=false + givenCanShowAlternateBouncer() + whenever(keyguardStateController.isUnlocked).thenReturn(false) + keyguardStateControllerCallbackCaptor.value.onUnlockedChanged() + + // THEN the alternate bouncer is still visible + assertTrue(bouncerRepository.alternateBouncerVisible.value) + } + + @Test + fun onUnlockedChangedIsTrue_hide() { + // GIVEN alternate bouncer is showing + bouncerRepository.setAlternateVisible(true) + + val keyguardStateControllerCallbackCaptor = + ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture()) + + // WHEN isUnlocked=true + givenCanShowAlternateBouncer() + whenever(keyguardStateController.isUnlocked).thenReturn(true) + keyguardStateControllerCallbackCaptor.value.onUnlockedChanged() + + // THEN the alternate bouncer is hidden + assertFalse(bouncerRepository.alternateBouncerVisible.value) + } + private fun givenCanShowAlternateBouncer() { bouncerRepository.setAlternateBouncerUIAvailable(true) biometricSettingsRepository.setFingerprintEnrolled(true) @@ -176,6 +225,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true) deviceEntryFingerprintAuthRepository.setLockedOut(false) whenever(keyguardStateController.isUnlocked).thenReturn(false) + whenever(statusBarStateController.isDozing).thenReturn(false) } private fun givenCannotShowAlternateBouncer() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt index 85e8d072bd99..6c3d6f533ace 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Icon +import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo.Companion.DEFAULT_ICON_TINT import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -140,6 +141,7 @@ class MediaTttUtilsTest : SysuiTestCase() { context.getString(R.string.media_transfer_receiver_content_description_unknown_app) ) assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast)) + assertThat(iconInfo.tint).isEqualTo(DEFAULT_ICON_TINT) } @Test @@ -232,40 +234,40 @@ class MediaTttUtilsTest : SysuiTestCase() { fun iconInfo_toTintedIcon_loaded() { val contentDescription = ContentDescription.Loaded("test") val drawable = context.getDrawable(R.drawable.ic_cake)!! - val tintAttr = android.R.attr.textColorTertiary + val tint = R.color.GM2_blue_500 val iconInfo = IconInfo( contentDescription, MediaTttIcon.Loaded(drawable), - tintAttr, + tint, isAppIcon = false, ) val tinted = iconInfo.toTintedIcon() assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription)) - assertThat(tinted.tintAttr).isEqualTo(tintAttr) + assertThat(tinted.tint).isEqualTo(tint) } @Test fun iconInfo_toTintedIcon_resource() { val contentDescription = ContentDescription.Loaded("test") val drawableRes = R.drawable.ic_cake - val tintAttr = android.R.attr.textColorTertiary + val tint = R.color.GM2_blue_500 val iconInfo = IconInfo( contentDescription, MediaTttIcon.Resource(drawableRes), - tintAttr, + tint, isAppIcon = false ) val tinted = iconInfo.toTintedIcon() assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription)) - assertThat(tinted.tintAttr).isEqualTo(tintAttr) + assertThat(tinted.tint).isEqualTo(tint) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt index 415e68f6013d..bcc99bc8dd0c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeInteractorTest.kt @@ -73,6 +73,28 @@ class MultiShadeInteractorTest : SysuiTestCase() { } @Test + fun isAnyShadeExpanded() = + testScope.runTest { + val underTest = create() + val isAnyShadeExpanded: Boolean? by collectLastValue(underTest.isAnyShadeExpanded) + assertWithMessage("isAnyShadeExpanded must start with false!") + .that(isAnyShadeExpanded) + .isFalse() + + underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0.441f) + assertThat(isAnyShadeExpanded).isTrue() + + underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0.442f) + assertThat(isAnyShadeExpanded).isTrue() + + underTest.setExpansion(shadeId = ShadeId.RIGHT, expansion = 0f) + assertThat(isAnyShadeExpanded).isTrue() + + underTest.setExpansion(shadeId = ShadeId.LEFT, expansion = 0f) + assertThat(isAnyShadeExpanded).isFalse() + } + + @Test fun isVisible_dualShadeConfig() = testScope.runTest { overrideResource(R.bool.dual_shade_enabled, true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt new file mode 100644 index 000000000000..f807146cdf12 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.multishade.domain.interactor + +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy +import com.android.systemui.multishade.data.repository.MultiShadeRepository +import com.android.systemui.multishade.shared.model.ProxiedInputModel +import com.android.systemui.multishade.shared.model.ShadeId +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class MultiShadeMotionEventInteractorTest : SysuiTestCase() { + + private lateinit var underTest: MultiShadeMotionEventInteractor + + private lateinit var testScope: TestScope + private lateinit var motionEvents: MutableSet<MotionEvent> + private lateinit var repository: MultiShadeRepository + private lateinit var interactor: MultiShadeInteractor + private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop + + @Before + fun setUp() { + testScope = TestScope() + motionEvents = mutableSetOf() + + val inputProxy = MultiShadeInputProxy() + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ) + interactor = + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = repository, + inputProxy = inputProxy, + ) + underTest = + MultiShadeMotionEventInteractor( + applicationContext = context, + applicationScope = testScope.backgroundScope, + interactor = interactor, + ) + } + + @After + fun tearDown() { + motionEvents.forEach { motionEvent -> motionEvent.recycle() } + } + + @Test + fun shouldIntercept_initialDown_returnsFalse() = + testScope.runTest { + assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse() + } + + @Test + fun shouldIntercept_moveBelowTouchSlop_returnsFalse() = + testScope.runTest { + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + y = touchSlop - 1f, + ) + ) + ) + .isFalse() + } + + @Test + fun shouldIntercept_moveAboveTouchSlop_returnsTrue() = + testScope.runTest { + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + y = touchSlop + 1f, + ) + ) + ) + .isTrue() + } + + @Test + fun shouldIntercept_moveAboveTouchSlop_butHorizontalFirst_returnsFalse() = + testScope.runTest { + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + x = touchSlop + 1f, + ) + ) + ) + .isFalse() + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + y = touchSlop + 1f, + ) + ) + ) + .isFalse() + } + + @Test + fun shouldIntercept_up_afterMovedAboveTouchSlop_returnsTrue() = + testScope.runTest { + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f)) + + assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isTrue() + } + + @Test + fun shouldIntercept_cancel_afterMovedAboveTouchSlop_returnsTrue() = + testScope.runTest { + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop + 1f)) + + assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isTrue() + } + + @Test + fun shouldIntercept_moveAboveTouchSlopAndUp_butShadeExpanded_returnsFalse() = + testScope.runTest { + repository.setExpansion(ShadeId.LEFT, 0.1f) + runCurrent() + + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + y = touchSlop + 1f, + ) + ) + ) + .isFalse() + assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP))).isFalse() + } + + @Test + fun shouldIntercept_moveAboveTouchSlopAndCancel_butShadeExpanded_returnsFalse() = + testScope.runTest { + repository.setExpansion(ShadeId.LEFT, 0.1f) + runCurrent() + + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + + assertThat( + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_MOVE, + y = touchSlop + 1f, + ) + ) + ) + .isFalse() + assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_CANCEL))).isFalse() + } + + @Test + fun tap_doesNotSendProxiedInput() = + testScope.runTest { + val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT)) + val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT)) + val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE)) + + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP)) + + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + } + + @Test + fun dragBelowTouchSlop_doesNotSendProxiedInput() = + testScope.runTest { + val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT)) + val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT)) + val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE)) + + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN)) + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_MOVE, y = touchSlop - 1f)) + underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_UP)) + + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + } + + @Test + fun dragAboveTouchSlopAndUp() = + testScope.runTest { + val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT)) + val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT)) + val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE)) + + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_DOWN, + x = 100f, // left shade + ) + ) + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + + val yDragAmountPx = touchSlop + 1f + val moveEvent = + motionEvent( + MotionEvent.ACTION_MOVE, + x = 100f, // left shade + y = yDragAmountPx, + ) + assertThat(underTest.shouldIntercept(moveEvent)).isTrue() + underTest.onTouchEvent(moveEvent, viewWidthPx = 1000) + assertThat(leftShadeProxiedInput) + .isEqualTo( + ProxiedInputModel.OnDrag( + xFraction = 0.1f, + yDragAmountPx = yDragAmountPx, + ) + ) + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + + val upEvent = motionEvent(MotionEvent.ACTION_UP) + assertThat(underTest.shouldIntercept(upEvent)).isTrue() + underTest.onTouchEvent(upEvent, viewWidthPx = 1000) + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + } + + @Test + fun dragAboveTouchSlopAndCancel() = + testScope.runTest { + val leftShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.LEFT)) + val rightShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.RIGHT)) + val singleShadeProxiedInput by collectLastValue(interactor.proxiedInput(ShadeId.SINGLE)) + + underTest.shouldIntercept( + motionEvent( + MotionEvent.ACTION_DOWN, + x = 900f, // right shade + ) + ) + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + + val yDragAmountPx = touchSlop + 1f + val moveEvent = + motionEvent( + MotionEvent.ACTION_MOVE, + x = 900f, // right shade + y = yDragAmountPx, + ) + assertThat(underTest.shouldIntercept(moveEvent)).isTrue() + underTest.onTouchEvent(moveEvent, viewWidthPx = 1000) + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput) + .isEqualTo( + ProxiedInputModel.OnDrag( + xFraction = 0.9f, + yDragAmountPx = yDragAmountPx, + ) + ) + assertThat(singleShadeProxiedInput).isNull() + + val cancelEvent = motionEvent(MotionEvent.ACTION_CANCEL) + assertThat(underTest.shouldIntercept(cancelEvent)).isTrue() + underTest.onTouchEvent(cancelEvent, viewWidthPx = 1000) + assertThat(leftShadeProxiedInput).isNull() + assertThat(rightShadeProxiedInput).isNull() + assertThat(singleShadeProxiedInput).isNull() + } + + private fun TestScope.motionEvent( + action: Int, + downTime: Long = currentTime, + eventTime: Long = currentTime, + x: Float = 0f, + y: Float = 0f, + ): MotionEvent { + val motionEvent = MotionEvent.obtain(downTime, eventTime, action, x, y, 0) + motionEvents.add(motionEvent) + return motionEvent + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt new file mode 100644 index 000000000000..893530982926 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/shared/math/MathTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.multishade.shared.math + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class MathTest : SysuiTestCase() { + + @Test + fun isZero_zero_true() { + assertThat(0f.isZero(epsilon = EPSILON)).isTrue() + } + + @Test + fun isZero_belowPositiveEpsilon_true() { + assertThat((EPSILON * 0.999999f).isZero(epsilon = EPSILON)).isTrue() + } + + @Test + fun isZero_aboveNegativeEpsilon_true() { + assertThat((EPSILON * -0.999999f).isZero(epsilon = EPSILON)).isTrue() + } + + @Test + fun isZero_positiveEpsilon_false() { + assertThat(EPSILON.isZero(epsilon = EPSILON)).isFalse() + } + + @Test + fun isZero_negativeEpsilon_false() { + assertThat((-EPSILON).isZero(epsilon = EPSILON)).isFalse() + } + + @Test + fun isZero_positive_false() { + assertThat(1f.isZero(epsilon = EPSILON)).isFalse() + } + + @Test + fun isZero_negative_false() { + assertThat((-1f).isZero(epsilon = EPSILON)).isFalse() + } + + companion object { + private const val EPSILON = 0.0001f + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 0a8cd269139f..0ee52ea7838a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -46,6 +46,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.isNull import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @@ -267,7 +268,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { verifyZeroInteractions(context) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle)) + verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), + isNull()) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) @@ -401,7 +403,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle)) + verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), + isNull()) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) @@ -424,7 +427,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(entryPoint = NoteTaskEntryPoint.QUICK_AFFORDANCE) val intentCaptor = argumentCaptor<Intent>() - verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle)) + verify(bubbles).showOrHideAppBubble(capture(intentCaptor), eq(userTracker.userHandle), + isNull()) intentCaptor.value.let { intent -> assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt index 1f18d91aa133..08b5d2bdc31c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotDetectionControllerTest.kt @@ -19,12 +19,14 @@ package com.android.systemui.screenshot import android.content.ComponentName import android.content.pm.ActivityInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS import android.testing.AndroidTestingRunner import android.view.Display import android.view.IWindowManager import android.view.WindowManager import androidx.test.filters.SmallTest import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import junit.framework.Assert.assertEquals @@ -32,6 +34,7 @@ import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatcher import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -158,4 +161,56 @@ class ScreenshotDetectionControllerTest { assertEquals(appName2, list[1]) assertEquals(appName3, list[2]) } + + private fun includesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) = + ComponentInfoFlagMatcher(mask, mask) + private fun excludesFlagBits(@PackageManager.ComponentInfoFlagsBits mask: Int) = + ComponentInfoFlagMatcher(mask, 0) + + private class ComponentInfoFlagMatcher( + @PackageManager.ComponentInfoFlagsBits val mask: Int, val value: Int + ): ArgumentMatcher<PackageManager.ComponentInfoFlags> { + override fun matches(flags: PackageManager.ComponentInfoFlags): Boolean { + return (mask.toLong() and flags.value) == value.toLong() + } + + override fun toString(): String{ + return "mask 0x%08x == 0x%08x".format(mask, value) + } + } + + @Test + fun testMaybeNotifyOfScreenshot_disabledApp() { + val data = ScreenshotData.forTesting() + data.source = WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD + + val component = ComponentName("package1", "class1") + val appName = "app name" + val activityInfo = mock(ActivityInfo::class.java) + + whenever( + packageManager.getActivityInfo( + eq(component), + argThat(includesFlagBits(MATCH_DISABLED_COMPONENTS)) + ) + ).thenReturn(activityInfo); + + whenever( + packageManager.getActivityInfo( + eq(component), + argThat(excludesFlagBits(MATCH_DISABLED_COMPONENTS)) + ) + ).thenThrow(PackageManager.NameNotFoundException::class.java); + + whenever(windowManager.notifyScreenshotListeners(eq(Display.DEFAULT_DISPLAY))) + .thenReturn(listOf(component)) + + whenever(activityInfo.loadLabel(eq(packageManager))).thenReturn(appName) + + val list = controller.maybeNotifyOfScreenshot(data) + + assertEquals(1, list.size) + assertEquals(appName, list[0]) + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 629208e130af..5f34b2f0f87f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy import com.android.systemui.multishade.data.repository.MultiShadeRepository import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.NotificationInsetsController @@ -62,7 +63,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -129,6 +129,16 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { val inputProxy = MultiShadeInputProxy() testScope = TestScope() + val multiShadeInteractor = + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) underTest = NotificationShadeWindowViewController( lockscreenShadeTransitionController, @@ -154,18 +164,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, + { multiShadeInteractor }, + FakeSystemClock(), { - MultiShadeInteractor( + MultiShadeMotionEventInteractor( + applicationContext = context, applicationScope = testScope.backgroundScope, - repository = - MultiShadeRepository( - applicationContext = context, - inputProxy = inputProxy, - ), - inputProxy = inputProxy, + interactor = multiShadeInteractor, ) }, - FakeSystemClock(), ) underTest.setupExpandedStatusBar() @@ -308,7 +315,7 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { // down event should be intercepted by keyguardViewManager whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(true) + .thenReturn(true) // Then touch should not be intercepted val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) @@ -316,14 +323,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test - fun testGetBouncerContainer() = - testScope.runTest { - Mockito.clearInvocations(view) - underTest.bouncerContainer - verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container) - } - - @Test fun testGetKeyguardMessageArea() = testScope.runTest { underTest.keyguardMessageArea diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index b4b5ec126234..b40181e24e6d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy import com.android.systemui.multishade.data.repository.MultiShadeRepository import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor +import com.android.systemui.multishade.domain.interactor.MultiShadeMotionEventInteractor import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.statusbar.DragDownHelper import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -140,6 +141,16 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { featureFlags.set(Flags.DUAL_SHADE, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() + val multiShadeInteractor = + MultiShadeInteractor( + applicationScope = testScope.backgroundScope, + repository = + MultiShadeRepository( + applicationContext = context, + inputProxy = inputProxy, + ), + inputProxy = inputProxy, + ) controller = NotificationShadeWindowViewController( lockscreenShadeTransitionController, @@ -165,18 +176,15 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, + { multiShadeInteractor }, + FakeSystemClock(), { - MultiShadeInteractor( + MultiShadeMotionEventInteractor( + applicationContext = context, applicationScope = testScope.backgroundScope, - repository = - MultiShadeRepository( - applicationContext = context, - inputProxy = inputProxy, - ), - inputProxy = inputProxy, + interactor = multiShadeInteractor, ) }, - FakeSystemClock(), ) controller.setupExpandedStatusBar() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt new file mode 100644 index 000000000000..64fec5bfd4ed --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shade + +import android.animation.Animator +import android.testing.AndroidTestingRunner +import android.transition.TransitionValues +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardStatusViewController +import com.android.systemui.SysuiTestCase +import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class SplitShadeTransitionAdapterTest : SysuiTestCase() { + + @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController + + private lateinit var adapter: SplitShadeTransitionAdapter + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + adapter = SplitShadeTransitionAdapter(keyguardStatusViewController) + } + + @Test + fun createAnimator_nullStartValues_returnsNull() { + val animator = adapter.createAnimator(startValues = null, endValues = TransitionValues()) + + assertThat(animator).isNull() + } + + @Test + fun createAnimator_nullEndValues_returnsNull() { + val animator = adapter.createAnimator(startValues = TransitionValues(), endValues = null) + + assertThat(animator).isNull() + } + + @Test + fun createAnimator_nonNullStartAndEndValues_returnsAnimator() { + val animator = + adapter.createAnimator(startValues = TransitionValues(), endValues = TransitionValues()) + + assertThat(animator).isNotNull() + } +} + +private fun SplitShadeTransitionAdapter.createAnimator( + startValues: TransitionValues?, + endValues: TransitionValues? +): Animator? { + return createAnimator(/* sceneRoot= */ null, startValues, endValues) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt index 6a68b71f639b..8841f481695d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt @@ -65,8 +65,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { // Positive translationX -> translated to the right // 10x10 view center is 25px from the center, // When progress is 0.5 it should be translated at: - // 25 * 0.3 * (1 - 0.5) = 3.75px - assertThat(view.translationX).isWithin(0.01f).of(3.75f) + // 25 * 0.08 * (1 - 0.5) = 1px + assertThat(view.translationX).isWithin(0.01f).of(1.0f) } @Test @@ -81,8 +81,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { // Positive translationX -> translated to the right // 10x10 view center is 25px from the center, // When progress is 0 it should be translated at: - // 25 * 0.3 * (1 - 0) = 7.5px - assertThat(view.translationX).isWithin(0.01f).of(7.5f) + // 25 * 0.08 * (1 - 0) = 7.5px + assertThat(view.translationX).isWithin(0.01f).of(2f) } @Test @@ -97,7 +97,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { // Positive translationX -> translated to the right // 10x10 view center is 25px from the center, // When progress is 1 it should be translated at: - // 25 * 0.3 * 0 = 0px + // 25 * 0.08 * 0 = 0px assertThat(view.translationX).isEqualTo(0f) } @@ -113,8 +113,8 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { // Positive translationX -> translated to the right, original translation is ignored // 10x10 view center is 25px from the center, // When progress is 0.5 it should be translated at: - // 25 * 0.3 * (1 - 0.5) = 3.75px - assertThat(view.translationX).isWithin(0.01f).of(3.75f) + // 25 * 0.08 * (1 - 0.5) = 1px + assertThat(view.translationX).isWithin(0.01f).of(1.0f) } @Test @@ -154,7 +154,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { animator.onTransitionProgress(0.5f) // Positive translationY -> translated to the bottom - assertThat(view.translationY).isWithin(0.01f).of(3.75f) + assertThat(view.translationY).isWithin(0.01f).of(1f) } @Test @@ -169,7 +169,7 @@ class UnfoldMoveFromCenterAnimatorTest : SysuiTestCase() { animator.updateViewPositions() // Negative translationX -> translated to the left - assertThat(view.translationX).isWithin(0.1f).of(-5.25f) + assertThat(view.translationX).isWithin(0.1f).of(-1.4f) } private fun createView( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 653b0c707240..09b00e246eec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -858,6 +858,23 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { } @Test + public void testShouldNotScreen_appSuspended() throws RemoteException { + NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); + when(mPowerManager.isInteractive()).thenReturn(false); + when(mStatusBarStateController.isDreaming()).thenReturn(false); + when(mStatusBarStateController.getState()).thenReturn(SHADE); + modifyRanking(entry).setSuspended(true).build(); + + assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry)) + .isEqualTo(FullScreenIntentDecision.NO_FSI_SUSPENDED); + assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) + .isFalse(); + verify(mLogger).logNoFullscreen(entry, "NO_FSI_SUSPENDED"); + verify(mLogger, never()).logNoFullscreenWarning(any(), any()); + verify(mLogger, never()).logFullscreen(any(), any()); + } + + @Test public void logFullScreenIntentDecision_shouldAlmostAlwaysLogOneTime() { NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false); Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java index 3108ed9e7b98..fe1205161e8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; +import android.view.View; import androidx.test.filters.SmallTest; @@ -49,6 +50,13 @@ public class KeyguardStatusBarViewTest extends SysuiTestCase { } @Test + public void userSwitcherChip_defaultVisibilityIsGone() { + assertThat(mKeyguardStatusBarView.findViewById( + R.id.user_switcher_container).getVisibility()).isEqualTo( + View.GONE); + } + + @Test public void setTopClipping_clippingUpdated() { int topClipping = 40; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt index cdc989897c8e..3edf33b9d3a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt @@ -25,6 +25,8 @@ import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.shade.NotificationPanelViewController import com.android.systemui.shade.ShadeControllerImpl import com.android.systemui.shade.ShadeLogger @@ -34,6 +36,7 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever import com.android.systemui.util.view.ViewUtil import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -54,6 +57,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController @Mock + private lateinit var featureFlags: FeatureFlags + @Mock private lateinit var moveFromCenterAnimation: StatusBarMoveFromCenterAnimationController @Mock private lateinit var sysuiUnfoldComponent: SysUIUnfoldComponent @@ -93,6 +98,8 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { @Test fun onViewAttachedAndDrawn_moveFromCenterAnimationEnabled_moveFromCenterAnimationInitialized() { + whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) + .thenReturn(true) val view = createViewMock() val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java) unfoldConfig.isEnabled = true @@ -108,6 +115,20 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { } @Test + fun onViewAttachedAndDrawn_statusBarAnimationDisabled_animationNotInitialized() { + whenever(featureFlags.isEnabled(Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS)) + .thenReturn(false) + val view = createViewMock() + unfoldConfig.isEnabled = true + // create the controller on main thread as it requires main looper + InstrumentationRegistry.getInstrumentation().runOnMainSync { + controller = createAndInitController(view) + } + + verify(moveFromCenterAnimation, never()).onViewsReady(any()) + } + + @Test fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() { `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false) val returnVal = view.onTouchEvent( @@ -179,6 +200,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() { return PhoneStatusBarViewController.Factory( Optional.of(sysuiUnfoldComponent), Optional.of(progressProvider), + featureFlags, userChipViewModel, centralSurfacesImpl, shadeControllerImpl, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt index 3bc288a2f823..08e89fbef486 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImplTest.kt @@ -20,13 +20,17 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl.EXTERNAL_SLOT_SUFFIX +import com.android.systemui.util.mockito.kotlinArgumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.mockito.Mock import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations @SmallTest class StatusBarIconControllerImplTest : SysuiTestCase() { @@ -34,15 +38,19 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { private lateinit var underTest: StatusBarIconControllerImpl private lateinit var iconList: StatusBarIconList + private lateinit var commandQueueCallbacks: CommandQueue.Callbacks private val iconGroup: StatusBarIconController.IconManager = mock() + @Mock private lateinit var commandQueue: CommandQueue + @Before fun setUp() { + MockitoAnnotations.initMocks(this) iconList = StatusBarIconList(arrayOf()) underTest = StatusBarIconControllerImpl( context, - mock(), + commandQueue, mock(), mock(), mock(), @@ -51,11 +59,42 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { mock(), ) underTest.addIconGroup(iconGroup) + val commandQueueCallbacksCaptor = kotlinArgumentCaptor<CommandQueue.Callbacks>() + verify(commandQueue).addCallback(commandQueueCallbacksCaptor.capture()) + commandQueueCallbacks = commandQueueCallbacksCaptor.value + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_externalFromTile_bothDisplayed() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val externalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 2, + /* iconLevel= */ 0, + /* number= */ 0, + "contentDescription", + ) + underTest.setIconFromTile(slotName, externalIcon) + + assertThat(iconList.slots).hasSize(2) + // Whichever was added last comes first + assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(iconList.slots[1].name).isEqualTo(slotName) + assertThat(iconList.slots[0].hasIconsInSlot()).isTrue() + assertThat(iconList.slots[1].hasIconsInSlot()).isTrue() } /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_bothDisplayed() { + fun internalAndExternalIconWithSameName_externalFromCommandQueue_bothDisplayed() { val slotName = "mute" // Internal @@ -71,7 +110,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "contentDescription", ) - underTest.setIcon(slotName, externalIcon) + commandQueueCallbacks.setIcon(slotName, externalIcon) assertThat(iconList.slots).hasSize(2) // Whichever was added last comes first @@ -83,17 +122,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveIcon_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromCommandQueue_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + commandQueueCallbacks.setIcon(slotName, createExternalIcon()) - // WHEN the external icon is removed via #removeIcon - underTest.removeIcon(slotName) + // WHEN the external icon is removed via CommandQueue.Callbacks#removeIcon + commandQueueCallbacks.removeIcon(slotName) // THEN the external icon is removed but the internal icon remains // Note: [StatusBarIconList] never removes slots from its list, it just sets the holder for @@ -109,17 +148,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaRemoveAll_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromTileRemove_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) - // WHEN the external icon is removed via #removeAllIconsForExternalSlot - underTest.removeAllIconsForExternalSlot(slotName) + // WHEN the external icon is removed via #removeIconForTile + underTest.removeIconForTile(slotName) // THEN the external icon is removed but the internal icon remains assertThat(iconList.slots).hasSize(2) @@ -133,17 +172,17 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalRemoved_viaSetNull_internalStays() { + fun internalAndExternalIconWithSameName_externalRemoved_fromTileSetNull_internalStays() { val slotName = "mute" // Internal underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) - // WHEN the external icon is removed via a #setIcon(null) - underTest.setIcon(slotName, /* icon= */ null) + // WHEN the external icon is removed via a #setIconFromTile(null) + underTest.setIconFromTile(slotName, /* icon= */ null) // THEN the external icon is removed but the internal icon remains assertThat(iconList.slots).hasSize(2) @@ -164,12 +203,12 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) // WHEN the internal icon is removed via #removeIcon underTest.removeIcon(slotName, /* tag= */ 0) - // THEN the external icon is removed but the internal icon remains + // THEN the internal icon is removed but the external icon remains assertThat(iconList.slots).hasSize(2) assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) assertThat(iconList.slots[1].name).isEqualTo(slotName) @@ -188,12 +227,12 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") // External - underTest.setIcon(slotName, createExternalIcon()) + underTest.setIconFromTile(slotName, createExternalIcon()) // WHEN the internal icon is removed via #removeAllIconsForSlot underTest.removeAllIconsForSlot(slotName) - // THEN the external icon is removed but the internal icon remains + // THEN the internal icon is removed but the external icon remains assertThat(iconList.slots).hasSize(2) assertThat(iconList.slots[0].name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) assertThat(iconList.slots[1].name).isEqualTo(slotName) @@ -221,7 +260,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "externalDescription", ) - underTest.setIcon(slotName, startingExternalIcon) + underTest.setIconFromTile(slotName, startingExternalIcon) // WHEN the internal icon is updated underTest.setIcon(slotName, /* resourceId= */ 11, "newContentDescription") @@ -243,7 +282,54 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /** Regression test for b/255428281. */ @Test - fun internalAndExternalIconWithSameName_externalUpdatedIndependently() { + fun internalAndExternalIconWithSameName_fromTile_externalUpdatedIndependently() { + val slotName = "mute" + + // Internal + underTest.setIcon(slotName, /* resourceId= */ 10, "contentDescription") + + // External + val startingExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 20, + /* iconLevel= */ 0, + /* number= */ 0, + "externalDescription", + ) + underTest.setIconFromTile(slotName, startingExternalIcon) + + // WHEN the external icon is updated + val newExternalIcon = + StatusBarIcon( + "external.package", + UserHandle.ALL, + /* iconId= */ 21, + /* iconLevel= */ 0, + /* number= */ 0, + "newExternalDescription", + ) + underTest.setIconFromTile(slotName, newExternalIcon) + + // THEN only the external slot gets the updates + val externalSlot = iconList.slots[0] + val externalHolder = externalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(externalSlot.name).isEqualTo(slotName + EXTERNAL_SLOT_SUFFIX) + assertThat(externalHolder.icon!!.contentDescription).isEqualTo("newExternalDescription") + assertThat(externalHolder.icon!!.icon.resId).isEqualTo(21) + + // And the internal slot has its own values + val internalSlot = iconList.slots[1] + val internalHolder = internalSlot.getHolderForTag(TAG_PRIMARY)!! + assertThat(internalSlot.name).isEqualTo(slotName) + assertThat(internalHolder.icon!!.contentDescription).isEqualTo("contentDescription") + assertThat(internalHolder.icon!!.icon.resId).isEqualTo(10) + } + + /** Regression test for b/255428281. */ + @Test + fun internalAndExternalIconWithSameName_fromCommandQueue_externalUpdatedIndependently() { val slotName = "mute" // Internal @@ -259,7 +345,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "externalDescription", ) - underTest.setIcon(slotName, startingExternalIcon) + commandQueueCallbacks.setIcon(slotName, startingExternalIcon) // WHEN the external icon is updated val newExternalIcon = @@ -271,7 +357,7 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { /* number= */ 0, "newExternalDescription", ) - underTest.setIcon(slotName, newExternalIcon) + commandQueueCallbacks.setIcon(slotName, newExternalIcon) // THEN only the external slot gets the updates val externalSlot = iconList.slots[0] @@ -289,8 +375,16 @@ class StatusBarIconControllerImplTest : SysuiTestCase() { } @Test - fun externalSlot_alreadyEndsWithSuffix_suffixNotAddedTwice() { - underTest.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) + fun externalSlot_fromTile_alreadyEndsWithSuffix_suffixNotAddedTwice() { + underTest.setIconFromTile("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) + + assertThat(iconList.slots).hasSize(1) + assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX") + } + + @Test + fun externalSlot_fromCommandQueue_alreadyEndsWithSuffix_suffixNotAddedTwice() { + commandQueueCallbacks.setIcon("myslot$EXTERNAL_SLOT_SUFFIX", createExternalIcon()) assertThat(iconList.slots).hasSize(1) assertThat(iconList.slots[0].name).isEqualTo("myslot$EXTERNAL_SLOT_SUFFIX") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index d9546877a861..14aee4e13a8f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -154,7 +154,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer); when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea); when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class))) .thenReturn(mKeyguardMessageAreaController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 64545b16a2af..be0c83f910f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -294,6 +294,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + public void userChip_defaultVisibilityIsGone() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + assertEquals(View.GONE, getUserChipView().getVisibility()); + } + + @Test public void disable_noOngoingCall_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -333,6 +340,19 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test + public void disable_hasOngoingCallButAlsoHun_chipHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + + when(mOngoingCallController.hasOngoingCall()).thenReturn(true); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.GONE, + mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + } + + @Test public void disable_ongoingCallEnded_chipHidden() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); @@ -558,6 +578,10 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { return (CollapsedStatusBarFragment) mFragment; } + private View getUserChipView() { + return mFragment.getView().findViewById(R.id.user_switcher_container); + } + private View getClockView() { return mFragment.getView().findViewById(R.id.clock); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt index 4525ad27b749..17f8ec2136a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusManagerTest.kt @@ -507,11 +507,29 @@ class StylusManagerTest : SysuiTestCase() { stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) verify(uiEventLogger, times(1)) - .logWithInstanceId( + .logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, 0, null, - InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId) + InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId), + 0, + ) + } + + @Test + fun onBatteryStateChanged_batteryPresent_btStylusPresent_logsSessionStart() { + whenever(batteryState.isPresent).thenReturn(true) + stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID) + + stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) + + verify(uiEventLogger, times(1)) + .logWithInstanceIdAndPosition( + StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, + 0, + null, + InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId), + 1, ) } @@ -545,19 +563,21 @@ class StylusManagerTest : SysuiTestCase() { stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState) verify(uiEventLogger, times(1)) - .logWithInstanceId( + .logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED, 0, null, - instanceId + instanceId, + 0 ) verify(uiEventLogger, times(1)) - .logWithInstanceId( + .logWithInstanceIdAndPosition( StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED, 0, null, - instanceId + instanceId, + 0 ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 586bdc6c8215..6e24941ac937 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -685,7 +685,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() { allowSwipeToDismiss: Boolean = false, ): ChipbarInfo { return ChipbarInfo( - TintedIcon(startIcon, tintAttr = null), + TintedIcon(startIcon, tint = null), text, endItem, vibrationEffect, diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt index 0413d92b6abb..9fe2f5694dde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt @@ -40,7 +40,7 @@ class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() { @Before fun setUp() { - progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(foldStateProvider) + progressProvider = PhysicsBasedUnfoldTransitionProgressProvider(context, foldStateProvider) progressProvider.addCallback(listener) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java index 075f393df15a..84129beea92a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java @@ -16,10 +16,16 @@ package com.android.systemui.util.sensors; +import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import android.content.res.Resources; +import android.hardware.Sensor; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -46,28 +52,52 @@ public class PostureDependentProximitySensorTest extends SysuiTestCase { @Mock private Resources mResources; @Mock private DevicePostureController mDevicePostureController; @Mock private AsyncSensorManager mSensorManager; + @Mock private Sensor mMockedPrimaryProxSensor; @Captor private ArgumentCaptor<DevicePostureController.Callback> mPostureListenerCaptor = ArgumentCaptor.forClass(DevicePostureController.Callback.class); private DevicePostureController.Callback mPostureListener; - private PostureDependentProximitySensor mProximitySensor; - private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); + private PostureDependentProximitySensor mPostureDependentProximitySensor; + private ThresholdSensor[] mPrimaryProxSensors; + private ThresholdSensor[] mSecondaryProxSensors; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); allowTestableLooperAsMainThread(); - mProximitySensor = new PostureDependentProximitySensor( - new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE], - new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE], - mFakeExecutor, + setupProximitySensors(DEVICE_POSTURE_CLOSED); + mPostureDependentProximitySensor = new PostureDependentProximitySensor( + mPrimaryProxSensors, + mSecondaryProxSensors, + new FakeExecutor(new FakeSystemClock()), new FakeExecution(), mDevicePostureController ); } + /** + * Support a proximity sensor only for the given devicePosture for the primary sensor. + * Otherwise, all other postures don't support prox. + */ + private void setupProximitySensors( + @DevicePostureController.DevicePostureInt int proxExistsForPosture) { + final ThresholdSensorImpl.Builder sensorBuilder = new ThresholdSensorImpl.BuilderFactory( + mResources, mSensorManager, new FakeExecution()).createBuilder(); + + mPrimaryProxSensors = new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE]; + mSecondaryProxSensors = + new ThresholdSensor[DevicePostureController.SUPPORTED_POSTURES_SIZE]; + for (int i = 0; i < DevicePostureController.SUPPORTED_POSTURES_SIZE; i++) { + mPrimaryProxSensors[i] = sensorBuilder.setSensor(null).setThresholdValue(0).build(); + mSecondaryProxSensors[i] = sensorBuilder.setSensor(null).setThresholdValue(0).build(); + } + + mPrimaryProxSensors[proxExistsForPosture] = sensorBuilder + .setSensor(mMockedPrimaryProxSensor).setThresholdValue(5).build(); + } + @Test public void testPostureChangeListenerAdded() { capturePostureListener(); @@ -83,30 +113,59 @@ public class PostureDependentProximitySensorTest extends SysuiTestCase { // THEN device posture is updated to DEVICE_POSTURE_OPENED assertEquals(DevicePostureController.DEVICE_POSTURE_OPENED, - mProximitySensor.mDevicePosture); + mPostureDependentProximitySensor.mDevicePosture); // WHEN the posture changes to DEVICE_POSTURE_CLOSED - mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_CLOSED); + mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED); // THEN device posture is updated to DEVICE_POSTURE_CLOSED - assertEquals(DevicePostureController.DEVICE_POSTURE_CLOSED, - mProximitySensor.mDevicePosture); + assertEquals(DEVICE_POSTURE_CLOSED, + mPostureDependentProximitySensor.mDevicePosture); // WHEN the posture changes to DEVICE_POSTURE_FLIPPED mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_FLIPPED); // THEN device posture is updated to DEVICE_POSTURE_FLIPPED assertEquals(DevicePostureController.DEVICE_POSTURE_FLIPPED, - mProximitySensor.mDevicePosture); + mPostureDependentProximitySensor.mDevicePosture); // WHEN the posture changes to DEVICE_POSTURE_HALF_OPENED mPostureListener.onPostureChanged(DevicePostureController.DEVICE_POSTURE_HALF_OPENED); // THEN device posture is updated to DEVICE_POSTURE_HALF_OPENED assertEquals(DevicePostureController.DEVICE_POSTURE_HALF_OPENED, - mProximitySensor.mDevicePosture); + mPostureDependentProximitySensor.mDevicePosture); + } + + @Test + public void proxSensorRegisters_proxSensorValid() { + // GIVEN posture that supports a valid posture with a prox sensor + capturePostureListener(); + mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED); + + // WHEN a listener registers + mPostureDependentProximitySensor.register(mock(ThresholdSensor.Listener.class)); + + // THEN PostureDependentProximitySensor is registered + assertTrue(mPostureDependentProximitySensor.isRegistered()); + } + + @Test + public void proxSensorReregisters_postureChangesAndNewlySupportsProx() { + // GIVEN there's a registered listener but posture doesn't support prox + assertFalse(mPostureDependentProximitySensor.isRegistered()); + mPostureDependentProximitySensor.register(mock(ThresholdSensor.Listener.class)); + assertFalse(mPostureDependentProximitySensor.isRegistered()); + + // WHEN posture that supports a valid posture with a prox sensor + capturePostureListener(); + mPostureListener.onPostureChanged(DEVICE_POSTURE_CLOSED); + + // THEN PostureDependentProximitySensor is registered + assertTrue(mPostureDependentProximitySensor.isRegistered()); } + private void capturePostureListener() { verify(mDevicePostureController).addCallback(mPostureListenerCaptor.capture()); mPostureListener = mPostureListenerCaptor.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index ee4e00baafe6..cc3b4ab0fb4e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -284,6 +284,8 @@ public class BubblesTest extends SysuiTestCase { private ShadeWindowLogger mShadeWindowLogger; @Mock private NotifPipelineFlags mNotifPipelineFlags; + @Mock + private Icon mAppBubbleIcon; private TestableBubblePositioner mPositioner; @@ -303,7 +305,7 @@ public class BubblesTest extends SysuiTestCase { // For the purposes of this test, just run everything synchronously ShellExecutor syncExecutor = new SyncExecutor(); - mUser0 = createUserHande(/* userId= */ 0); + mUser0 = createUserHandle(/* userId= */ 0); when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors); when(mNotificationShadeWindowView.getViewTreeObserver()) @@ -1250,7 +1252,7 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowManageMenuChangesSysuiState_appBubble() { - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); assertTrue(mBubbleController.hasBubbles()); // Expand the stack @@ -1671,7 +1673,7 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleController.isStackExpanded()).isFalse(); assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true), /* showInShade= */ eq(false)); @@ -1681,13 +1683,13 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowOrHideAppBubble_expandIfCollapsed() { - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.collapseStack(); assertThat(mBubbleController.isStackExpanded()).isFalse(); // Calling this while collapsed will expand the app bubble - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isTrue(); @@ -1696,12 +1698,12 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowOrHideAppBubble_collapseIfSelected() { - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isTrue(); // Calling this while the app bubble is expanded should collapse the stack - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isFalse(); @@ -1711,15 +1713,15 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowOrHideAppBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() { - UserHandle user10 = createUserHande(/* userId = */ 10); - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10); + UserHandle user10 = createUserHandle(/* userId = */ 10); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isTrue(); assertThat(mBubbleData.getBubbles().size()).isEqualTo(1); assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10); // Calling this while the app bubble is expanded should collapse the stack - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isFalse(); @@ -1729,13 +1731,13 @@ public class BubblesTest extends SysuiTestCase { @Test public void testShowOrHideAppBubble_selectIfNotSelected() { - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); mBubbleController.updateBubble(mBubbleEntry); mBubbleController.expandStackAndSelectBubble(mBubbleEntry); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey()); assertThat(mBubbleController.isStackExpanded()).isTrue(); - mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0); + mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon); assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(KEY_APP_BUBBLE); assertThat(mBubbleController.isStackExpanded()).isTrue(); assertThat(mBubbleData.getBubbles().size()).isEqualTo(2); @@ -1870,7 +1872,7 @@ public class BubblesTest extends SysuiTestCase { mBubbleController.onUserChanged(userId); } - private UserHandle createUserHande(int userId) { + private UserHandle createUserHandle(int userId) { UserHandle user = mock(UserHandle.class); when(user.getIdentifier()).thenReturn(userId); return user; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java index 926c6c56a862..c664c99cf2a7 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java @@ -47,17 +47,17 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void setExternalIcon(String slot) { + public void setIconFromTile(String slot, StatusBarIcon icon) { } @Override - public void setIcon(String slot, int resourceId, CharSequence contentDescription) { + public void removeIconForTile(String slot) { } @Override - public void setIcon(String slot, StatusBarIcon icon) { + public void setIcon(String slot, int resourceId, CharSequence contentDescription) { } @@ -98,10 +98,6 @@ public class FakeStatusBarIconController extends BaseLeakChecker<IconManager> } @Override - public void removeAllIconsForExternalSlot(String slot) { - } - - @Override public void setIconAccessibilityLiveRegion(String slot, int mode) { } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt index 28e493651137..f8f168bd4dc1 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt @@ -15,8 +15,15 @@ */ package com.android.systemui.unfold.progress +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context import android.os.Trace +import android.util.FloatProperty import android.util.Log +import android.view.animation.AnimationUtils.loadInterpolator import androidx.dynamicanimation.animation.DynamicAnimation import androidx.dynamicanimation.animation.FloatPropertyCompat import androidx.dynamicanimation.animation.SpringAnimation @@ -34,14 +41,22 @@ import com.android.systemui.unfold.updates.name import javax.inject.Inject /** Maps fold updates to unfold transition progress using DynamicAnimation. */ -class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( - private val foldStateProvider: FoldStateProvider -) : UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { +class PhysicsBasedUnfoldTransitionProgressProvider +@Inject +constructor(context: Context, private val foldStateProvider: FoldStateProvider) : + UnfoldTransitionProgressProvider, FoldUpdatesListener, DynamicAnimation.OnAnimationEndListener { + + private val emphasizedInterpolator = + loadInterpolator(context, android.R.interpolator.fast_out_extra_slow_in) + + private var cannedAnimator: ValueAnimator? = null private val springAnimation = - SpringAnimation(this, AnimationProgressProperty).apply { - addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) - } + SpringAnimation( + this, + FloatPropertyCompat.createFloatPropertyCompat(AnimationProgressProperty) + ) + .apply { addEndListener(this@PhysicsBasedUnfoldTransitionProgressProvider) } private var isTransitionRunning = false private var isAnimatedCancelRunning = false @@ -76,7 +91,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( override fun onFoldUpdate(@FoldUpdate update: Int) { when (update) { - FOLD_UPDATE_FINISH_FULL_OPEN, FOLD_UPDATE_FINISH_HALF_OPEN -> { + FOLD_UPDATE_FINISH_FULL_OPEN, + FOLD_UPDATE_FINISH_HALF_OPEN -> { // Do not cancel if we haven't started the transition yet. // This could happen when we fully unfolded the device before the screen // became available. In this case we start and immediately cancel the animation @@ -100,6 +116,14 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( // the transition continues running. if (isAnimatedCancelRunning) { isAnimatedCancelRunning = false + + // Switching to spring animation, start the animation if it + // is not running already + springAnimation.animateToFinalPosition(1.0f) + + cannedAnimator?.removeAllListeners() + cannedAnimator?.cancel() + cannedAnimator = null } } else { startTransition(startValue = 1f) @@ -130,13 +154,22 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( } isAnimatedCancelRunning = true - springAnimation.animateToFinalPosition(endValue) + + if (USE_CANNED_ANIMATION) { + startCannedCancelAnimation() + } else { + springAnimation.animateToFinalPosition(endValue) + } } else { transitionProgress = endValue isAnimatedCancelRunning = false isTransitionRunning = false springAnimation.cancel() + cannedAnimator?.removeAllListeners() + cannedAnimator?.cancel() + cannedAnimator = null + listeners.forEach { it.onTransitionFinished() } if (DEBUG) { @@ -157,7 +190,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( } private fun onStartTransition() { - Trace.beginSection( "$TAG#onStartTransition") + Trace.beginSection("$TAG#onStartTransition") listeners.forEach { it.onTransitionStarted() } Trace.endSection() @@ -195,8 +228,39 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( listeners.remove(listener) } + private fun startCannedCancelAnimation() { + cannedAnimator?.cancel() + cannedAnimator = null + + // Temporary remove listener to cancel the spring animation without + // finishing the transition + springAnimation.removeEndListener(this) + springAnimation.cancel() + springAnimation.addEndListener(this) + + cannedAnimator = + ObjectAnimator.ofFloat(this, AnimationProgressProperty, transitionProgress, 1f).apply { + addListener(CannedAnimationListener()) + duration = + (CANNED_ANIMATION_DURATION_MS.toFloat() * (1f - transitionProgress)).toLong() + interpolator = emphasizedInterpolator + start() + } + } + + private inner class CannedAnimationListener : AnimatorListenerAdapter() { + override fun onAnimationStart(animator: Animator) { + Trace.beginAsyncSection("$TAG#cannedAnimatorRunning", 0) + } + + override fun onAnimationEnd(animator: Animator) { + cancelTransition(1f, animate = false) + Trace.endAsyncSection("$TAG#cannedAnimatorRunning", 0) + } + } + private object AnimationProgressProperty : - FloatPropertyCompat<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") { + FloatProperty<PhysicsBasedUnfoldTransitionProgressProvider>("animation_progress") { override fun setValue( provider: PhysicsBasedUnfoldTransitionProgressProvider, @@ -205,7 +269,7 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( provider.transitionProgress = value } - override fun getValue(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float = + override fun get(provider: PhysicsBasedUnfoldTransitionProgressProvider): Float = provider.transitionProgress } } @@ -213,6 +277,8 @@ class PhysicsBasedUnfoldTransitionProgressProvider @Inject constructor( private const val TAG = "PhysicsBasedUnfoldTransitionProgressProvider" private const val DEBUG = true +private const val USE_CANNED_ANIMATION = true +private const val CANNED_ANIMATION_DURATION_MS = 1000 private const val SPRING_STIFFNESS = 600.0f private const val MINIMAL_VISIBLE_CHANGE = 0.001f private const val FINAL_HINGE_ANGLE_POSITION = 165f diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index ee806a67ce42..1298f638b01c 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -2076,6 +2076,7 @@ public class CameraExtensionsProxyService extends Service { ret.outputId.id = output.getId(); ret.physicalCameraId = output.getPhysicalCameraId(); ret.surfaceGroupId = output.getSurfaceGroupId(); + ret.isMultiResolutionOutput = false; if (output instanceof SurfaceOutputConfigImpl) { SurfaceOutputConfigImpl surfaceConfig = (SurfaceOutputConfigImpl) output; ret.type = CameraOutputConfig.TYPE_SURFACE; @@ -2095,6 +2096,7 @@ public class CameraExtensionsProxyService extends Service { ret.type = CameraOutputConfig.TYPE_MULTIRES_IMAGEREADER; ret.imageFormat = multiResReaderConfig.getImageFormat(); ret.capacity = multiResReaderConfig.getMaxImages(); + ret.isMultiResolutionOutput = true; } else { throw new IllegalStateException("Unknown output config type!"); } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 01386da08314..af5b196fe93d 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -383,7 +383,7 @@ public final class AutofillManagerService final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId); if (service == null) { // If we cannot get the service from the services cache, it will call - // updateRemoteAugmentedAutofillService() finally. Skip call this update again. + // updateRemoteFieldClassificationService() finally. Skip call this update again. getServiceForUserLocked(userId); } else { service.updateRemoteFieldClassificationService(); diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index bea049cb92dd..d5dcdaf3c7b0 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -1731,7 +1731,7 @@ final class AutofillManagerServiceImpl } /** - * Called when the {@link AutofillManagerService#mAugmentedAutofillResolver} + * Called when the {@link AutofillManagerService#mFieldClassificationResolver} * changed (among other places). */ void updateRemoteFieldClassificationService() { @@ -1742,7 +1742,6 @@ final class AutofillManagerServiceImpl + "destroying old remote service"); } mRemoteFieldClassificationService.unbind(); - mRemoteFieldClassificationService = null; mRemoteFieldClassificationServiceInfo = null; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index b4e636e602d4..62a29705f62e 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -327,13 +327,11 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { private int setTemporaryDetectionService(PrintWriter pw) { final int userId = getNextIntArgRequired(); final String serviceName = getNextArg(); - final int duration = getNextIntArgRequired(); - if (serviceName == null) { mService.resetTemporaryDetectionService(userId); return 0; } - + final int duration = getNextIntArgRequired(); if (duration <= 0) { mService.resetTemporaryDetectionService(userId); return 0; diff --git a/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java new file mode 100644 index 000000000000..3b30af69d02b --- /dev/null +++ b/services/autofill/java/com/android/server/autofill/FillRequestEventLogger.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.autofill; + +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_EXPLICITLY_REQUESTED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_NORMAL_TRIGGER; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_PRE_TRIGGER; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_RETRIGGER; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN; +import static com.android.server.autofill.Helper.sVerbose; + +import android.annotation.IntDef; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.util.FrameworkStatsLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Optional; + +/** + * Helper class to log Autofill FillRequest filing stats. + */ +public final class FillRequestEventLogger { + private static final String TAG = "FillRequestEventLogger"; + + /** + * Reasons why presentation was not shown. These are wrappers around + * {@link com.android.os.AtomsProto.AutofillFillRequestReported.RequestTriggerReason}. + */ + @IntDef(prefix = {"TRIGGER_REASON"}, value = { + TRIGGER_REASON_UNKNOWN, + TRIGGER_REASON_EXPLICITLY_REQUESTED, + TRIGGER_REASON_RETRIGGER, + TRIGGER_REASON_PRE_TRIGGER, + TRIGGER_REASON_NORMAL_TRIGGER, + TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE + }) + @Retention(RetentionPolicy.SOURCE) + public @interface TriggerReason { + } + + public static final int TRIGGER_REASON_UNKNOWN = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN; + public static final int TRIGGER_REASON_EXPLICITLY_REQUESTED = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_EXPLICITLY_REQUESTED; + public static final int TRIGGER_REASON_RETRIGGER = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_RETRIGGER; + public static final int TRIGGER_REASON_PRE_TRIGGER = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_PRE_TRIGGER; + public static final int TRIGGER_REASON_NORMAL_TRIGGER = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_NORMAL_TRIGGER; + public static final int TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE; + + private final int mSessionId; + private Optional<FillRequestEventInternal> mEventInternal; + + private FillRequestEventLogger(int sessionId) { + mSessionId = sessionId; + mEventInternal = Optional.empty(); + } + + /** + * A factory constructor to create FillRequestEventLogger. + */ + public static FillRequestEventLogger forSessionId(int sessionId) { + return new FillRequestEventLogger(sessionId); + } + /** + * Reset mEventInternal before logging for a new request. It shall be called for each + * FillRequest. + */ + public void startLogForNewRequest() { + if (!mEventInternal.isEmpty()) { + Slog.w(TAG, "FillRequestEventLogger is not empty before starting for a new " + + "request"); + } + mEventInternal = Optional.of(new FillRequestEventInternal()); + } + + /** + * Set request_id as long as mEventInternal presents. + */ + public void maybeSetRequestId(int requestId) { + mEventInternal.ifPresent(event -> event.mRequestId = requestId); + } + + /** + * Set service_uid as long as mEventInternal presents. + */ + public void maybeSetAutofillServiceUid(int uid) { + mEventInternal.ifPresent(event -> { + event.mAutofillServiceUid = uid; + }); + } + + /** + * Set inline_suggestion_host_uid as long as mEventInternal presents. + */ + public void maybeSetInlineSuggestionHostUid(Context context, int userId) { + mEventInternal.ifPresent(event -> { + String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), + Settings.Secure.DEFAULT_INPUT_METHOD, userId); + if (TextUtils.isEmpty(imeString)) { + Slog.w(TAG, "No default IME found"); + return; + } + ComponentName imeComponent = ComponentName.unflattenFromString(imeString); + if (imeComponent == null) { + Slog.w(TAG, "No default IME found"); + return; + } + int imeUid; + String packageName = imeComponent.getPackageName(); + try { + imeUid = context.getPackageManager().getApplicationInfoAsUser(packageName, + PackageManager.ApplicationInfoFlags.of(0), userId).uid; + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "Couldn't find packageName: " + packageName); + return; + } + event.mInlineSuggestionHostUid = imeUid; + }); + } + + + /** + * Set flags as long as mEventInternal presents. + */ + public void maybeSetFlags(int flags) { + mEventInternal.ifPresent(event -> { + event.mFlags = flags; + }); + } + + /** + * Set request_trigger_reason as long as mEventInternal presents. + */ + public void maybeSetRequestTriggerReason(@TriggerReason int reason) { + mEventInternal.ifPresent(event -> { + event.mRequestTriggerReason = reason; + }); + } + + /** + * Set is_augmented as long as mEventInternal presents. + */ + public void maybeSetIsAugmented(boolean val) { + mEventInternal.ifPresent(event -> { + event.mIsAugmented = val; + }); + } + + /** + * Set is_client_suggestion as long as mEventInternal presents. + */ + public void maybeSetIsClientSuggestionFallback(boolean val) { + mEventInternal.ifPresent(event -> { + event.mIsClientSuggestionFallback = val; + }); + } + + /** + * Set is_fill_dialog_eligible as long as mEventInternal presents. + */ + public void maybeSetIsFillDialogEligible(boolean val) { + mEventInternal.ifPresent(event -> { + event.mIsFillDialogEligible = val; + }); + } + + /** + * Set latency_fill_request_sent_millis as long as mEventInternal presents. + */ + public void maybeSetLatencyFillRequestSentMillis(int timestamp) { + mEventInternal.ifPresent(event -> { + event.mLatencyFillRequestSentMillis = timestamp; + }); + } + + /** + * Set app_package_uid as long as mEventInternal presents. + */ + public void maybeSetAppPackageUid(int uid) { + mEventInternal.ifPresent(event -> { + event.mAppPackageUid = uid; + }); + } + + /** + * Log an AUTOFILL_FILL_REQUEST_REPORTED event. + */ + public void logAndEndEvent() { + if (!mEventInternal.isPresent()) { + Slog.w(TAG, "Shouldn't be logging AutofillFillRequestReported again for same " + + "event"); + return; + } + FillRequestEventInternal event = mEventInternal.get(); + if (sVerbose) { + Slog.v(TAG, "Log AutofillFillRequestReported:" + + " requestId=" + event.mRequestId + + " sessionId=" + mSessionId + + " mAutofillServiceUid=" + event.mAutofillServiceUid + + " mInlineSuggestionHostUid=" + event.mInlineSuggestionHostUid + + " mIsAugmented=" + event.mIsAugmented + + " mIsClientSuggestionFallback=" + event.mIsClientSuggestionFallback + + " mIsFillDialogEligible=" + event.mIsFillDialogEligible + + " mRequestTriggerReason=" + event.mRequestTriggerReason + + " mFlags=" + event.mFlags + + " mLatencyFillRequestSentMillis=" + event.mLatencyFillRequestSentMillis + + " mAppPackageUid=" + event.mAppPackageUid); + } + FrameworkStatsLog.write( + AUTOFILL_FILL_REQUEST_REPORTED, + event.mRequestId, + mSessionId, + event.mAutofillServiceUid, + event.mInlineSuggestionHostUid, + event.mIsAugmented, + event.mIsClientSuggestionFallback, + event.mIsFillDialogEligible, + event.mRequestTriggerReason, + event.mFlags, + event.mLatencyFillRequestSentMillis, + event.mAppPackageUid); + mEventInternal = Optional.empty(); + } + + private static final class FillRequestEventInternal { + int mRequestId; + int mAppPackageUid = -1; + int mAutofillServiceUid = -1; + int mInlineSuggestionHostUid = -1; + boolean mIsAugmented = false; + boolean mIsClientSuggestionFallback = false; + boolean mIsFillDialogEligible = false; + int mRequestTriggerReason = + AUTOFILL_FILL_REQUEST_REPORTED__REQUEST_TRIGGER_REASON__TRIGGER_REASON_UNKNOWN; + int mFlags = -1; + int mLatencyFillRequestSentMillis = -1; + + FillRequestEventInternal() { + } + } +} diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index 590f472e8b22..7d1de40c7150 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -38,6 +38,7 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_SESSION_COMMITTED_PREMATURELY; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_UNKNOWN_REASON; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; +import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE; import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; import static com.android.server.autofill.Helper.sVerbose; @@ -71,6 +72,7 @@ public final class PresentationStatsEventLogger { @IntDef(prefix = {"NOT_SHOWN_REASON"}, value = { NOT_SHOWN_REASON_ANY_SHOWN, NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED, + NOT_SHOWN_REASON_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE, NOT_SHOWN_REASON_VIEW_CHANGED, NOT_SHOWN_REASON_ACTIVITY_FINISHED, NOT_SHOWN_REASON_REQUEST_TIMEOUT, @@ -86,6 +88,8 @@ public final class PresentationStatsEventLogger { AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__ANY_SHOWN; public static final int NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUS_CHANGED; + public static final int NOT_SHOWN_REASON_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE = + AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_FOCUSED_BEFORE_FILL_DIALOG_RESPONSE; public static final int NOT_SHOWN_REASON_VIEW_CHANGED = AUTOFILL_PRESENTATION_EVENT_REPORTED__PRESENTATION_EVENT_RESULT__NONE_SHOWN_VIEW_CHANGED; public static final int NOT_SHOWN_REASON_ACTIVITY_FINISHED = @@ -142,7 +146,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetAvailableCount(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int availableCount = getDatasetCountForAutofillId(datasetList, currentViewId); event.mAvailableCount = availableCount; @@ -151,7 +155,7 @@ public final class PresentationStatsEventLogger { } public void maybeSetCountShown(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { mEventInternal.ifPresent(event -> { int countShown = getDatasetCountForAutofillId(datasetList, currentViewId); event.mCountShown = countShown; @@ -162,7 +166,7 @@ public final class PresentationStatsEventLogger { } private static int getDatasetCountForAutofillId(@Nullable List<Dataset> datasetList, - AutofillId currentViewId) { + AutofillId currentViewId) { int availableCount = 0; if (datasetList != null) { for (int i = 0; i < datasetList.size(); i++) { @@ -225,10 +229,34 @@ public final class PresentationStatsEventLogger { }); } + public void maybeSetSelectedDatasetId(int selectedDatasetId) { + mEventInternal.ifPresent(event -> { + event.mSelectedDatasetId = selectedDatasetId; + }); + } + + public void maybeSetDialogDismissed(boolean dialogDismissed) { + mEventInternal.ifPresent(event -> { + event.mDialogDismissed = dialogDismissed; + }); + } + + public void maybeSetNegativeCtaButtonClicked(boolean negativeCtaButtonClicked) { + mEventInternal.ifPresent(event -> { + event.mNegativeCtaButtonClicked = negativeCtaButtonClicked; + }); + } + + public void maybeSetPositiveCtaButtonClicked(boolean positiveCtaButtonClicked) { + mEventInternal.ifPresent(event -> { + event.mPositiveCtaButtonClicked = positiveCtaButtonClicked; + }); + } + public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) { mEventInternal.ifPresent(event -> { event.mDisplayPresentationType = - AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; + AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE; String imeString = Settings.Secure.getStringForUser(context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD, userId); if (TextUtils.isEmpty(imeString)) { @@ -290,7 +318,11 @@ public final class PresentationStatsEventLogger { + " mFillRequestSentTimestampMs=" + event.mFillRequestSentTimestampMs + " mFillResponseReceivedTimestampMs=" + event.mFillResponseReceivedTimestampMs + " mSuggestionSentTimestampMs=" + event.mSuggestionSentTimestampMs - + " mSuggestionPresentedTimestampMs=" + event.mSuggestionPresentedTimestampMs); + + " mSuggestionPresentedTimestampMs=" + event.mSuggestionPresentedTimestampMs + + " mSelectedDatasetId=" + event.mSelectedDatasetId + + " mDialogDismissed=" + event.mDialogDismissed + + " mNegativeCtaButtonClicked=" + event.mNegativeCtaButtonClicked + + " mPositiveCtaButtonClicked=" + event.mPositiveCtaButtonClicked); } // TODO(b/234185326): Distinguish empty responses from other no presentation reasons. @@ -316,11 +348,10 @@ public final class PresentationStatsEventLogger { event.mFillResponseReceivedTimestampMs, event.mSuggestionSentTimestampMs, event.mSuggestionPresentedTimestampMs, - //TODO(b/265051751): add new framework logging. - /* selected_dataset_id= */ 0, - /* dialog_dismissed= */ false, - /* negative_cta_button_clicked= */ false, - /* positive_cta_button_clicked= */ false); + event.mSelectedDatasetId, + event.mDialogDismissed, + event.mNegativeCtaButtonClicked, + event.mPositiveCtaButtonClicked); mEventInternal = Optional.empty(); } @@ -341,6 +372,10 @@ public final class PresentationStatsEventLogger { int mFillResponseReceivedTimestampMs; int mSuggestionSentTimestampMs; int mSuggestionPresentedTimestampMs; + int mSelectedDatasetId = -1; + boolean mDialogDismissed = false; + boolean mNegativeCtaButtonClicked = false; + boolean mPositiveCtaButtonClicked = false; PresentationStatsEventInternal() {} } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 15a533c9958e..4acdabec92f4 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -41,6 +41,9 @@ import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; +import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_PRE_TRIGGER; +import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE; import static com.android.server.autofill.Helper.containsCharsInOrder; import static com.android.server.autofill.Helper.createSanitizers; import static com.android.server.autofill.Helper.getNumericValue; @@ -439,6 +442,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private PresentationStatsEventLogger mPresentationStatsEventLogger; + @NonNull + @GuardedBy("mLock") + private FillRequestEventLogger mFillRequestEventLogger; + /** * Fill dialog request would likely be sent slightly later. */ @@ -605,6 +612,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPendingInlineSuggestionsRequest = null; mWaitForInlineRequest = false; mPendingFillRequest = null; + + final long fillRequestSentRelativeTimestamp = + SystemClock.elapsedRealtime() - mLatencyBaseTime; + mPresentationStatsEventLogger.maybeSetFillRequestSentTimestampMs( + (int) (fillRequestSentRelativeTimestamp)); + mFillRequestEventLogger.maybeSetLatencyFillRequestSentMillis( + (int) (fillRequestSentRelativeTimestamp)); + mFillRequestEventLogger.logAndEndEvent(); } @Override @@ -1082,11 +1097,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, int flags) { final FillResponse existingResponse = viewState.getResponse(); + mFillRequestEventLogger.startLogForNewRequest(); + mFillRequestEventLogger.maybeSetAppPackageUid(uid); + mFillRequestEventLogger.maybeSetFlags(mFlags); + if(mPreviouslyFillDialogPotentiallyStarted) { + mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER); + } else { + mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER); + } if (existingResponse != null) { setViewStatesLocked( existingResponse, ViewState.STATE_INITIAL, /* clearResponse= */ true); + mFillRequestEventLogger.maybeSetRequestTriggerReason( + TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE); } mSessionFlags.mExpiredResponse = false; mSessionState = STATE_ACTIVE; @@ -1097,6 +1122,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", flags=" + flags + ")"); } mSessionFlags.mAugmentedAutofillOnly = true; + // Augmented autofill doesn't have request_id. + mFillRequestEventLogger.maybeSetRequestId(-1); + mFillRequestEventLogger.maybeSetIsAugmented(mSessionFlags.mAugmentedAutofillOnly); + mFillRequestEventLogger.logAndEndEvent(); triggerAugmentedAutofillLocked(flags); return; } @@ -1123,6 +1152,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + ", flags=" + flags); } mPresentationStatsEventLogger.maybeSetRequestId(requestId); + mFillRequestEventLogger.maybeSetRequestId(requestId); + mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); + if (mSessionFlags.mInlineSupportedByService) { + mFillRequestEventLogger.maybeSetInlineSuggestionHostUid(mContext, userId); + } + mFillRequestEventLogger.maybeSetIsFillDialogEligible(!mSessionFlags.mFillDialogDisabled); // If the focus changes very quickly before the first request is returned each focus change // triggers a new partition and we end up with many duplicate partitions. This is @@ -1189,11 +1224,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final long fillRequestSentRelativeTimestamp = - SystemClock.elapsedRealtime() - mLatencyBaseTime; - mPresentationStatsEventLogger.maybeSetFillRequestSentTimestampMs( - (int) (fillRequestSentRelativeTimestamp)); - // Now request the assist structure data. requestAssistStructureLocked(requestId, flags); } @@ -1284,6 +1314,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mCompatMode = compatMode; mSessionState = STATE_ACTIVE; mPresentationStatsEventLogger = PresentationStatsEventLogger.forSessionId(sessionId); + mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId); synchronized (mLock) { mSessionFlags = new SessionFlags(); mSessionFlags.mAugmentedAutofillOnly = forAugmentedAutofillOnly; diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 4a0a228ce645..c129938ea7b1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -602,7 +602,7 @@ public final class ActiveServices { try { final ServiceRecord.StartItem si = r.pendingStarts.get(0); startServiceInnerLocked(this, si.intent, r, false, true, si.callingId, - si.mCallingProcessName, r.startRequested); + si.mCallingProcessName, r.startRequested, si.mCallingPackageName); } catch (TransactionTooLargeException e) { // Ignore, nobody upstack cares. } @@ -969,7 +969,7 @@ public final class ActiveServices { startServiceInnerLocked(r, service, callingUid, callingPid, getCallingProcessNameLocked(callingUid, callingPid, callingPackage), fgRequired, callerFg, - backgroundStartPrivileges); + backgroundStartPrivileges, callingPackage); if (res.aliasComponent != null && !realResult.getPackageName().startsWith("!") && !realResult.getPackageName().startsWith("?")) { @@ -990,7 +990,7 @@ public final class ActiveServices { private ComponentName startServiceInnerLocked(ServiceRecord r, Intent service, int callingUid, int callingPid, String callingProcessName, boolean fgRequired, boolean callerFg, - BackgroundStartPrivileges backgroundStartPrivileges) + BackgroundStartPrivileges backgroundStartPrivileges, String callingPackage) throws TransactionTooLargeException { NeededUriGrants neededGrants = mAm.mUgmInternal.checkGrantUriPermissionFromIntent( service, callingUid, r.packageName, r.userId); @@ -1003,7 +1003,7 @@ public final class ActiveServices { r.delayedStop = false; r.fgRequired = fgRequired; r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - service, neededGrants, callingUid, callingProcessName)); + service, neededGrants, callingUid, callingProcessName, callingPackage)); if (fgRequired) { // We are now effectively running a foreground service. @@ -1088,7 +1088,7 @@ public final class ActiveServices { r.allowBgActivityStartsOnServiceStart(backgroundStartPrivileges); } ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting, - callingUid, callingProcessName, wasStartRequested); + callingUid, callingProcessName, wasStartRequested, callingPackage); return cmp; } @@ -1241,7 +1241,7 @@ public final class ActiveServices { try { startServiceInnerLocked(s, serviceIntent, callingUid, callingPid, callingProcessName, fgRequired, callerFg, - backgroundStartPrivileges); + backgroundStartPrivileges, callingPackage); } catch (TransactionTooLargeException e) { /* ignore - local call */ } @@ -1287,7 +1287,7 @@ public final class ActiveServices { ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r, boolean callerFg, boolean addToStarting, int callingUid, String callingProcessName, - boolean wasStartRequested) throws TransactionTooLargeException { + boolean wasStartRequested, String callingPackage) throws TransactionTooLargeException { synchronized (mAm.mProcessStats.mLock) { final ServiceState stracker = r.getTracker(); if (stracker != null) { @@ -1328,7 +1328,9 @@ public final class ActiveServices { : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM), getShortProcessNameForStats(callingUid, callingProcessName), getShortServiceNameForStats(r), - packageState); + packageState, + packageName, + callingPackage); if (r.startRequested && addToStarting) { boolean first = smap.mStartingBackground.size() == 0; @@ -3661,7 +3663,9 @@ public final class ActiveServices { : SERVICE_REQUEST_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM), getShortProcessNameForStats(callingUid, callerApp.processName), getShortServiceNameForStats(s), - packageState); + packageState, + s.packageName, + callerApp.info.packageName); if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bind " + s + " with " + b + ": received=" + b.intent.received @@ -5253,7 +5257,7 @@ public final class ActiveServices { // be called. if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), - null, null, 0, null)); + null, null, 0, null, null)); } sendServiceArgsLocked(r, execInFg, true); @@ -6247,7 +6251,7 @@ public final class ActiveServices { stopServiceLocked(sr, true); } else { sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, - sr.getLastStartId(), baseIntent, null, 0, null)); + sr.getLastStartId(), baseIntent, null, 0, null, null)); if (sr.app != null && sr.app.getThread() != null) { // We always run in the foreground, since this is called as // part of the "remove task" UI operation. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 59a41399e18c..9e5acf796025 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13933,7 +13933,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver( /*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) { throw new SecurityException("SDK sandbox not allowed to register receiver" - + " with the given IntentFilter"); + + " with the given IntentFilter: " + filter.toString()); } } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 2d779c4c85a1..6928bd307af0 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -106,7 +106,6 @@ import android.app.role.RoleManager; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -117,7 +116,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo.ForegroundServiceType; -import android.database.ContentObserver; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.AppBackgroundRestrictionsInfo; @@ -137,7 +135,6 @@ import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; import android.provider.Settings; -import android.provider.Settings.Global; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.ArraySet; @@ -1069,8 +1066,7 @@ public final class AppRestrictionController { } } - final class ConstantsObserver extends ContentObserver implements - OnPropertiesChangedListener { + final class ConstantsObserver implements OnPropertiesChangedListener { /** * Whether or not to set the app to restricted standby bucket automatically * when it's background-restricted. @@ -1181,8 +1177,6 @@ public final class AppRestrictionController { volatile boolean mBgAutoRestrictAbusiveApps; - volatile boolean mRestrictedBucketEnabled; - volatile long mBgAbusiveNotificationMinIntervalMs; volatile long mBgLongFgsNotificationMinIntervalMs; @@ -1215,7 +1209,6 @@ public final class AppRestrictionController { volatile boolean mBgPromptAbusiveAppsToBgRestricted; ConstantsObserver(Handler handler, Context context) { - super(handler); mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean( com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted); mDefaultBgPromptAbusiveAppToBgRestricted = context.getResources().getBoolean( @@ -1261,29 +1254,10 @@ public final class AppRestrictionController { } } - @Override - public void onChange(boolean selfChange) { - updateSettings(); - } - public void start() { - final ContentResolver cr = mContext.getContentResolver(); - cr.registerContentObserver(Global.getUriFor(Global.ENABLE_RESTRICTED_BUCKET), - false, this); - updateSettings(); updateDeviceConfig(); } - void updateSettings() { - mRestrictedBucketEnabled = isRestrictedBucketEnabled(); - } - - private boolean isRestrictedBucketEnabled() { - return Global.getInt(mContext.getContentResolver(), - Global.ENABLE_RESTRICTED_BUCKET, - Global.DEFAULT_ENABLE_RESTRICTED_BUCKET) == 1; - } - void updateDeviceConfig() { updateBgAutoRestrictedBucketChanged(); updateBgAutoRestrictAbusiveApps(); @@ -1763,8 +1737,7 @@ public final class AppRestrictionController { .isAppBackgroundRestricted(uid, packageName)) { return new Pair<>(RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, mEmptyTrackerInfo); } - level = mConstantsObserver.mRestrictedBucketEnabled - && standbyBucket == STANDBY_BUCKET_RESTRICTED + level = standbyBucket == STANDBY_BUCKET_RESTRICTED ? RESTRICTION_LEVEL_RESTRICTED_BUCKET : RESTRICTION_LEVEL_ADAPTIVE_BUCKET; if (calcTrackers) { @@ -1811,13 +1784,9 @@ public final class AppRestrictionController { @RestrictionLevel int level = RESTRICTION_LEVEL_UNKNOWN; @RestrictionLevel int prevLevel = level; BaseAppStateTracker resultTracker = null; - final boolean isRestrictedBucketEnabled = mConstantsObserver.mRestrictedBucketEnabled; for (int i = mAppStateTrackers.size() - 1; i >= 0; i--) { @RestrictionLevel int l = mAppStateTrackers.get(i).getPolicy() .getProposedRestrictionLevel(packageName, uid, maxLevel); - if (!isRestrictedBucketEnabled && l == RESTRICTION_LEVEL_RESTRICTED_BUCKET) { - l = RESTRICTION_LEVEL_ADAPTIVE_BUCKET; - } level = Math.max(level, l); if (level != prevLevel) { resultTracker = mAppStateTrackers.get(i); @@ -2193,9 +2162,6 @@ public final class AppRestrictionController { } if (level >= RESTRICTION_LEVEL_RESTRICTED_BUCKET && curLevel < RESTRICTION_LEVEL_RESTRICTED_BUCKET) { - if (!mConstantsObserver.mRestrictedBucketEnabled) { - return; - } // Moving the app standby bucket to restricted in the meanwhile. if (DEBUG_BG_RESTRICTION_CONTROLLER && level == RESTRICTION_LEVEL_BACKGROUND_RESTRICTED) { diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java index 33d4004a9027..4d469639be6f 100644 --- a/services/core/java/com/android/server/am/BroadcastConstants.java +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -242,7 +242,7 @@ public class BroadcastConstants { */ public boolean CORE_DEFER_UNTIL_ACTIVE = DEFAULT_CORE_DEFER_UNTIL_ACTIVE; private static final String KEY_CORE_DEFER_UNTIL_ACTIVE = "bcast_core_defer_until_active"; - private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = false; + private static final boolean DEFAULT_CORE_DEFER_UNTIL_ACTIVE = true; // Settings override tracking for this instance private String mSettingsKey; diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 1f6573076d51..0cdd4e9041e9 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -399,7 +399,8 @@ class BroadcastProcessQueue { * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ - public void setProcessCached(boolean cached) { + @VisibleForTesting + void setProcessCached(boolean cached) { if (mProcessCached != cached) { mProcessCached = cached; invalidateRunnableAt(); diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index c08570684aec..fcddff0e5ebc 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -601,7 +601,9 @@ public class BroadcastQueueImpl extends BroadcastQueue { r.dispatchTime - r.enqueueTime, r.receiverTime - r.dispatchTime, finishTime - r.receiverTime, - packageState); + packageState, + r.curApp.info.packageName, + r.callerPackage); } if (state == BroadcastRecord.IDLE) { Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE"); @@ -780,7 +782,8 @@ public class BroadcastQueueImpl extends BroadcastQueue { BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME, BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM, dispatchDelay, receiveDelay, 0 /* finish_delay */, - SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL); + SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL, + app != null ? app.info.packageName : null, callingPackage); } } diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index ff4f9b9795df..1f0b1628aa22 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1122,7 +1122,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } r.terminalCount++; - notifyFinishReceiver(queue, r, index, receiver); + notifyFinishReceiver(queue, app, r, index, receiver); checkFinished = true; } // When entire ordered broadcast finished, deliver final result @@ -1595,9 +1595,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { * typically for internal bookkeeping. */ private void notifyFinishReceiver(@Nullable BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index, @NonNull Object receiver) { + @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index, + @NonNull Object receiver) { if (r.wasDeliveryAttempted(index)) { - logBroadcastDeliveryEventReported(queue, r, index, receiver); + logBroadcastDeliveryEventReported(queue, app, r, index, receiver); } final boolean recordFinished = (r.terminalCount == r.receivers.size()); @@ -1607,7 +1608,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } private void logBroadcastDeliveryEventReported(@Nullable BroadcastProcessQueue queue, - @NonNull BroadcastRecord r, int index, @NonNull Object receiver) { + @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index, + @NonNull Object receiver) { // Report statistics for each individual receiver final int uid = getReceiverUid(receiver); final int senderUid = (r.callingUid == -1) ? Process.SYSTEM_UID : r.callingUid; @@ -1633,7 +1635,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL; FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, uid, senderUid, actionName, - receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState); + receiverType, type, dispatchDelay, receiveDelay, finishDelay, packageState, + app != null ? app.info.packageName : null, r.callerPackage); } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 3ab9e7171429..e8b65b86aed9 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1914,7 +1914,9 @@ public final class CachedAppOptimizer { uids[0] = uid; frozenStates[0] = frozen ? UID_FROZEN_STATE_FROZEN : UID_FROZEN_STATE_UNFROZEN; - Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen); + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, "reportOneUidFrozenStateChanged uid " + uid + " frozen = " + frozen); + } mAm.reportUidFrozenStateChanged(uids, frozenStates); } diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 48df1494fbe8..a1fcd424f8c1 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -263,7 +263,8 @@ public class ContentProviderHelper { PROVIDER_ACQUISITION_EVENT_REPORTED, r.uid, callingUid, PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM, - PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL); + PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL, + cpi.packageName, callingPackage); return holder; } @@ -334,7 +335,8 @@ public class ContentProviderHelper { PROVIDER_ACQUISITION_EVENT_REPORTED, cpr.proc.uid, callingUid, PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM, - PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL); + PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL, + cpi.packageName, callingPackage); } } finally { Binder.restoreCallingIdentity(origId); @@ -511,7 +513,8 @@ public class ContentProviderHelper { PROVIDER_ACQUISITION_EVENT_REPORTED, proc.uid, callingUid, PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM, - PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL); + PROVIDER_ACQUISITION_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL, + cpi.packageName, callingPackage); } else { final int packageState = ((cpr.appInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) @@ -536,7 +539,7 @@ public class ContentProviderHelper { PROVIDER_ACQUISITION_EVENT_REPORTED, proc.uid, callingUid, PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD, - packageState); + packageState, cpi.packageName, callingPackage); } cpr.launchingApp = proc; mLaunchingProviders.add(cpr); diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 663121ea3212..4defdc6976e1 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -249,6 +249,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN final String mCallingProcessName; final Intent intent; final NeededUriGrants neededGrants; + final @Nullable String mCallingPackageName; long deliveredTime; int deliveryCount; int doneExecutingCount; @@ -258,7 +259,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent, NeededUriGrants _neededGrants, int _callingId, - String callingProcessName) { + String callingProcessName, @Nullable String callingPackageName) { sr = _sr; taskRemoved = _taskRemoved; id = _id; @@ -266,6 +267,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN neededGrants = _neededGrants; callingId = _callingId; mCallingProcessName = callingProcessName; + mCallingPackageName = callingPackageName; } UriPermissionOwner getUriPermissionsLocked() { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 127a9d8b41ee..4e687f4929cf 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1046,9 +1046,14 @@ public class AudioService extends IAudioService.Stub mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase)); - final boolean headTrackingDefault = mContext.getResources().getBoolean( + final boolean binauralEnabledDefault = SystemProperties.getBoolean( + "ro.audio.spatializer_binaural_enabled_default", true); + final boolean transauralEnabledDefault = SystemProperties.getBoolean( + "ro.audio.spatializer_transaural_enabled_default", true); + final boolean headTrackingEnabledDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default); - mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, headTrackingDefault); + mSpatializerHelper = new SpatializerHelper(this, mAudioSystem, + binauralEnabledDefault, transauralEnabledDefault, headTrackingEnabledDefault); mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mHasVibrator = mVibrator == null ? false : mVibrator.hasVibrator(); @@ -7237,7 +7242,7 @@ public class AudioService extends IAudioService.Stub super.setDeviceVolumeBehavior_enforcePermission(); // verify arguments Objects.requireNonNull(device); - AudioManager.enforceSettableVolumeBehavior(deviceVolumeBehavior); + AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 8f54e45a1533..5edd43464c6b 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -172,13 +172,17 @@ public class SpatializerHelper { // initialization @SuppressWarnings("StaticAssignmentInConstructor") SpatializerHelper(@NonNull AudioService mother, @NonNull AudioSystemAdapter asa, - boolean headTrackingEnabledByDefault) { + boolean binauralEnabledDefault, + boolean transauralEnabledDefault, + boolean headTrackingEnabledDefault) { mAudioService = mother; mASA = asa; // "StaticAssignmentInConstructor" warning is suppressed as the SpatializerHelper being // constructed here is the factory for SADeviceState, thus SADeviceState and its // private static field sHeadTrackingEnabledDefault should never be accessed directly. - SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledByDefault; + SADeviceState.sBinauralEnabledDefault = binauralEnabledDefault; + SADeviceState.sTransauralEnabledDefault = transauralEnabledDefault; + SADeviceState.sHeadTrackingEnabledDefault = headTrackingEnabledDefault; } synchronized void init(boolean effectExpected, @Nullable String settings) { @@ -1547,10 +1551,12 @@ public class SpatializerHelper { } /*package*/ static final class SADeviceState { + private static boolean sBinauralEnabledDefault = true; + private static boolean sTransauralEnabledDefault = true; private static boolean sHeadTrackingEnabledDefault = false; final @AudioDeviceInfo.AudioDeviceType int mDeviceType; final @NonNull String mDeviceAddress; - boolean mEnabled = true; // by default, SA is enabled on any device + boolean mEnabled; boolean mHasHeadTracker = false; boolean mHeadTrackerEnabled; static final String SETTING_FIELD_SEPARATOR = ","; @@ -1566,6 +1572,12 @@ public class SpatializerHelper { SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) { mDeviceType = deviceType; mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : ""; + final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE); + mEnabled = spatMode == SpatializationMode.SPATIALIZER_BINAURAL + ? sBinauralEnabledDefault + : spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL + ? sTransauralEnabledDefault + : false; mHeadTrackerEnabled = sHeadTrackingEnabledDefault; } diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index ea157c89f675..e01aa9b33784 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1514,14 +1514,17 @@ public final class DisplayManagerService extends SystemService { } } - // When calling setContentRecordingSession into the WindowManagerService, the WMS + // When calling WindowManagerService#setContentRecordingSession, WindowManagerService // attempts to acquire a lock before executing its main body. Due to this, we need // to be sure that it isn't called while the DisplayManagerService is also holding // a lock, to avoid a deadlock scenario. final ContentRecordingSession session = virtualDisplayConfig.getContentRecordingSession(); - - if (displayId != Display.INVALID_DISPLAY && session != null) { + // Ensure session details are only set when mirroring (through VirtualDisplay flags or + // MediaProjection). + final boolean shouldMirror = + projection != null || (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0; + if (shouldMirror && displayId != Display.INVALID_DISPLAY && session != null) { // Only attempt to set content recording session if there are details to set and a // VirtualDisplay has been successfully constructed. session.setDisplayId(displayId); @@ -1529,8 +1532,8 @@ public final class DisplayManagerService extends SystemService { // We set the content recording session here on the server side instead of using // a second AIDL call in MediaProjection. By ensuring that a virtual display has // been constructed before calling setContentRecordingSession, we avoid a race - // condition between the DMS & WMS which could lead to the MediaProjection - // being pre-emptively torn down. + // condition between the DisplayManagerService & WindowManagerService which could + // lead to the MediaProjection being pre-emptively torn down. if (!mWindowManagerInternal.setContentRecordingSession(session)) { // Unable to start mirroring, so tear down projection & release VirtualDisplay. try { diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 3864200a8cb0..13e29a38efce 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -1444,13 +1444,12 @@ public class DisplayModeDirector { } public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) { - if (defaultPeakRefreshRate == null) { - defaultPeakRefreshRate = (float) mContext.getResources().getInteger( - R.integer.config_defaultPeakRefreshRate); - } - - if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { - synchronized (mLock) { + synchronized (mLock) { + if (defaultPeakRefreshRate == null) { + setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + updateRefreshRateSettingLocked(); + } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { mDefaultPeakRefreshRate = defaultPeakRefreshRate; updateRefreshRateSettingLocked(); } @@ -2115,11 +2114,20 @@ public class DisplayModeDirector { mLowDisplayBrightnessThresholds = displayThresholds; mLowAmbientBrightnessThresholds = ambientThresholds; } else { - // Invalid or empty. Use device default. - mLowDisplayBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_brightnessThresholdsOfPeakRefreshRate); - mLowAmbientBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_ambientThresholdsOfPeakRefreshRate); + DisplayDeviceConfig displayDeviceConfig; + synchronized (mLock) { + displayDeviceConfig = mDefaultDisplayDeviceConfig; + } + mLowDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), + R.array.config_brightnessThresholdsOfPeakRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + mLowAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), + R.array.config_ambientThresholdsOfPeakRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); } restartObserver(); } @@ -2129,7 +2137,15 @@ public class DisplayModeDirector { * DeviceConfig properties. */ public void onDeviceConfigRefreshRateInLowZoneChanged(int refreshRate) { - if (refreshRate != mRefreshRateInLowZone) { + if (refreshRate == -1) { + // Given there is no value available in DeviceConfig, lets not attempt loading it + // from there. + synchronized (mLock) { + loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + } + restartObserver(); + } else if (refreshRate != mRefreshRateInLowZone) { mRefreshRateInLowZone = refreshRate; restartObserver(); } @@ -2142,11 +2158,20 @@ public class DisplayModeDirector { mHighDisplayBrightnessThresholds = displayThresholds; mHighAmbientBrightnessThresholds = ambientThresholds; } else { - // Invalid or empty. Use device default. - mHighDisplayBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate); - mHighAmbientBrightnessThresholds = mContext.getResources().getIntArray( - R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate); + DisplayDeviceConfig displayDeviceConfig; + synchronized (mLock) { + displayDeviceConfig = mDefaultDisplayDeviceConfig; + } + mHighDisplayBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), + R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + mHighAmbientBrightnessThresholds = loadBrightnessThresholds( + () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), + R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, + displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); } restartObserver(); } @@ -2156,7 +2181,15 @@ public class DisplayModeDirector { * DeviceConfig properties. */ public void onDeviceConfigRefreshRateInHighZoneChanged(int refreshRate) { - if (refreshRate != mRefreshRateInHighZone) { + if (refreshRate == -1) { + // Given there is no value available in DeviceConfig, lets not attempt loading it + // from there. + synchronized (mLock) { + loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, + /* attemptLoadingFromDeviceConfig= */ false); + } + restartObserver(); + } else if (refreshRate != mRefreshRateInHighZone) { mRefreshRateInHighZone = refreshRate; restartObserver(); } @@ -3067,10 +3100,8 @@ public class DisplayModeDirector { new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) .sendToTarget(); - if (refreshRateInLowZone != -1) { - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, - 0).sendToTarget(); - } + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, + 0).sendToTarget(); int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); @@ -3080,10 +3111,8 @@ public class DisplayModeDirector { new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) .sendToTarget(); - if (refreshRateInHighZone != -1) { - mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, - 0).sendToTarget(); - } + mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, + 0).sendToTarget(); synchronized (mLock) { final int refreshRateInHbmSunlight = diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index b336b95d793f..7a0bf0cacdfb 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2003,7 +2003,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { @@ -2017,7 +2017,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { @@ -2040,7 +2040,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { @@ -2062,7 +2062,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission( + mContext.enforceCallingOrSelfPermission( Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } @@ -2158,7 +2158,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { @@ -3634,7 +3635,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); if (editorInfo == null || editorInfo.targetInputMethodUser == null || editorInfo.targetInputMethodUser.getIdentifier() != userId) { @@ -4115,7 +4117,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { if (mSettings.getCurrentUserId() == userId) { @@ -4133,7 +4136,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, @UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } final int callingUid = Binder.getCallingUid(); @@ -4186,7 +4190,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub public void setExplicitlyEnabledInputMethodSubtypes(String imeId, @NonNull int[] subtypeHashCodes, @UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } final int callingUid = Binder.getCallingUid(); final ComponentName imeComponentName = @@ -5412,7 +5417,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) { if (UserHandle.getCallingUserId() != userId) { - mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { if (mSettings.getCurrentUserId() == userId) { diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 647a89efcbbf..6f0903cf8685 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.telecom.TelecomManager; +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.internal.util.NotificationMessagingUtil; import java.util.Comparator; @@ -38,6 +39,7 @@ public class NotificationComparator private final Context mContext; private final NotificationMessagingUtil mMessagingUtil; + private final boolean mSortByInterruptiveness; private String mDefaultPhoneApp; public NotificationComparator(Context context) { @@ -45,6 +47,8 @@ public class NotificationComparator mContext.registerReceiver(mPhoneAppBroadcastReceiver, new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED)); mMessagingUtil = new NotificationMessagingUtil(mContext); + mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled( + SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS); } @Override @@ -135,10 +139,12 @@ public class NotificationComparator return -1 * Integer.compare(leftPriority, rightPriority); } - final boolean leftInterruptive = left.isInterruptive(); - final boolean rightInterruptive = right.isInterruptive(); - if (leftInterruptive != rightInterruptive) { - return -1 * Boolean.compare(leftInterruptive, rightInterruptive); + if (mSortByInterruptiveness) { + final boolean leftInterruptive = left.isInterruptive(); + final boolean rightInterruptive = right.isInterruptive(); + if (leftInterruptive != rightInterruptive) { + return -1 * Boolean.compare(leftInterruptive, rightInterruptive); + } } // then break ties by time, most recent first diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c607eca2249d..f09f7c2797e5 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -7803,7 +7803,8 @@ public class NotificationManagerService extends SystemService { */ @GuardedBy("mNotificationLock") @VisibleForTesting - protected boolean isVisuallyInterruptive(NotificationRecord old, NotificationRecord r) { + protected boolean isVisuallyInterruptive(@Nullable NotificationRecord old, + @NonNull NotificationRecord r) { // Ignore summary updates because we don't display most of the information. if (r.getSbn().isGroup() && r.getSbn().getNotification().isGroupSummary()) { if (DEBUG_INTERRUPTIVENESS) { @@ -7821,14 +7822,6 @@ public class NotificationManagerService extends SystemService { return true; } - if (r == null) { - if (DEBUG_INTERRUPTIVENESS) { - Slog.v(TAG, "INTERRUPTIVENESS: " - + r.getKey() + " is not interruptive: null"); - } - return false; - } - Notification oldN = old.getSbn().getNotification(); Notification newN = r.getSbn().getNotification(); if (oldN.extras == null || newN.extras == null) { @@ -7886,6 +7879,14 @@ public class NotificationManagerService extends SystemService { return true; } + if (Notification.areIconsDifferent(oldN, newN)) { + if (DEBUG_INTERRUPTIVENESS) { + Slog.v(TAG, "INTERRUPTIVENESS: " + + r.getKey() + " is interruptive: icons differ"); + } + return true; + } + // Fields below are invisible to bubbles. if (r.canBubble()) { if (DEBUG_INTERRUPTIVENESS) { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 7fe6c7d5aa93..cd1ae74e413c 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -3279,7 +3279,7 @@ final class InstallPackageHelper { final RemovePackageHelper removePackageHelper = new RemovePackageHelper(mPm); removePackageHelper.removePackage(stubPkg, true /*chatty*/); try { - return scanSystemPackageTracedLI(scanFile, parseFlags, scanFlags, null); + return initPackageTracedLI(scanFile, parseFlags, scanFlags); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to install compressed system package:" + stubPkg.getPackageName(), e); @@ -3410,8 +3410,7 @@ final class InstallPackageHelper { | ParsingPackageUtils.PARSE_MUST_BE_APK | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR; @PackageManagerService.ScanFlags int scanFlags = mPm.getSystemPackageScanFlags(codePath); - final AndroidPackage pkg = scanSystemPackageTracedLI( - codePath, parseFlags, scanFlags, null); + final AndroidPackage pkg = initPackageTracedLI(codePath, parseFlags, scanFlags); synchronized (mPm.mLock) { PackageSetting pkgSetting = mPm.mSettings.getPackageLPr(pkg.getPackageName()); @@ -3591,7 +3590,7 @@ final class InstallPackageHelper { try { final File codePath = new File(pkg.getPath()); synchronized (mPm.mInstallLock) { - scanSystemPackageTracedLI(codePath, 0, scanFlags, null); + initPackageTracedLI(codePath, 0, scanFlags); } } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse updated, ex-system package: " @@ -3734,12 +3733,6 @@ final class InstallPackageHelper { String errorMsg = null; if (throwable == null) { - // TODO(b/194319951): move lower in the scan chain - // Static shared libraries have synthetic package names - if (parseResult.parsedPackage.isStaticSharedLibrary()) { - PackageManagerService.renameStaticSharedLibraryPackage( - parseResult.parsedPackage); - } try { addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags, new UserHandle(UserHandle.USER_SYSTEM), apexInfo); @@ -3804,8 +3797,8 @@ final class InstallPackageHelper { try { synchronized (mPm.mInstallLock) { - final AndroidPackage newPkg = scanSystemPackageTracedLI( - scanFile, reparseFlags, rescanFlags, null); + final AndroidPackage newPkg = initPackageTracedLI( + scanFile, reparseFlags, rescanFlags); // We rescanned a stub, add it to the list of stubbed system packages if (newPkg.isStub()) { stubSystemApps.add(packageName); @@ -3819,28 +3812,26 @@ final class InstallPackageHelper { } /** - * Traces a package scan. - * @see #scanSystemPackageLI(File, int, int, UserHandle) + * Traces a package scan and registers it with the system. + * @see #initPackageLI(File, int, int) */ @GuardedBy("mPm.mInstallLock") - public AndroidPackage scanSystemPackageTracedLI(File scanFile, final int parseFlags, - int scanFlags, @Nullable ApexManager.ActiveApexInfo apexInfo) + public AndroidPackage initPackageTracedLI(File scanFile, final int parseFlags, int scanFlags) throws PackageManagerException { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage [" + scanFile.toString() + "]"); try { - return scanSystemPackageLI(scanFile, parseFlags, scanFlags, apexInfo); + return initPackageLI(scanFile, parseFlags, scanFlags); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } /** - * Scans a package and returns the newly parsed package. + * Scans a package, registers it with the system and returns the newly parsed package. * Returns {@code null} in case of errors and the error code is stored in mLastScanError */ @GuardedBy("mPm.mInstallLock") - private AndroidPackage scanSystemPackageLI(File scanFile, int parseFlags, int scanFlags, - @Nullable ApexManager.ActiveApexInfo apexInfo) + private AndroidPackage initPackageLI(File scanFile, int parseFlags, int scanFlags) throws PackageManagerException { if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanFile); @@ -3852,13 +3843,8 @@ final class InstallPackageHelper { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - // Static shared libraries have synthetic package names - if (parsedPackage.isStaticSharedLibrary()) { - PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage); - } - return addForInitLI(parsedPackage, parseFlags, scanFlags, - new UserHandle(UserHandle.USER_SYSTEM), apexInfo); + new UserHandle(UserHandle.USER_SYSTEM), null); } /** @@ -3882,6 +3868,10 @@ final class InstallPackageHelper { throws PackageManagerException { PackageSetting disabledPkgSetting; synchronized (mPm.mLock) { + // Static shared libraries have synthetic package names + if (activeApexInfo == null && parsedPackage.isStaticSharedLibrary()) { + PackageManagerService.renameStaticSharedLibraryPackage(parsedPackage); + } disabledPkgSetting = mPm.mSettings.getDisabledSystemPkgLPr(parsedPackage.getPackageName()); if (activeApexInfo != null && disabledPkgSetting != null) { diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 7684a4998d66..8f8f4376d2cc 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -159,8 +159,8 @@ public final class StorageEventHelper extends StorageEventListener { synchronized (mPm.mInstallLock) { final AndroidPackage pkg; try { - pkg = installPackageHelper.scanSystemPackageTracedLI( - ps.getPath(), parseFlags, SCAN_INITIAL, null); + pkg = installPackageHelper.initPackageTracedLI( + ps.getPath(), parseFlags, SCAN_INITIAL); loaded.add(pkg); } catch (PackageManagerException e) { diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java index e1461356cc4c..76a714c47f2e 100644 --- a/services/core/java/com/android/server/policy/GlobalActions.java +++ b/services/core/java/com/android/server/policy/GlobalActions.java @@ -93,7 +93,8 @@ class GlobalActions implements GlobalActionsProvider.GlobalActionsListener { mGlobalActionsAvailable = available; if (mShowing && !mGlobalActionsAvailable) { // Global actions provider died but we need to be showing global actions still, show the - // legacy global acrions provider. + // legacy global actions provider and remove timeout callbacks to avoid legacy re-show. + mHandler.removeCallbacks(mShowTimeout); ensureLegacyCreated(); mLegacyGlobalActions.showDialog(mKeyguardShowing, mDeviceProvisioned); } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 3eeafeb52b4b..b32e8f0b3221 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -1537,7 +1537,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { private void interceptScreenshotChord(int source, long pressDelay) { mHandler.removeMessages(MSG_SCREENSHOT_CHORD); - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source), + // arg2 is unused, but necessary to insure we call the correct method signature + // since the screenshot source is read from message.arg1 + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SCREENSHOT_CHORD, source, 0), pressDelay); } @@ -3555,16 +3557,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; } else { - setKeyguardOccludedLw(occluded, true /* notify */); + setKeyguardOccludedLw(occluded); } } @Override - public int applyKeyguardOcclusionChange(boolean notify) { + public int applyKeyguardOcclusionChange() { if (mKeyguardOccludedChanged) { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded changed occluded=" + mPendingKeyguardOccluded); - if (setKeyguardOccludedLw(mPendingKeyguardOccluded, notify)) { + if (setKeyguardOccludedLw(mPendingKeyguardOccluded)) { return FINISH_LAYOUT_REDO_LAYOUT | FINISH_LAYOUT_REDO_WALLPAPER; } } @@ -3583,8 +3585,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ private int handleTransitionForKeyguardLw(boolean startKeyguardExitAnimation, boolean notifyOccluded) { - final int redoLayout = applyKeyguardOcclusionChange(notifyOccluded); - if (redoLayout != 0) return redoLayout; + if (notifyOccluded) { + final int redoLayout = applyKeyguardOcclusionChange(); + if (redoLayout != 0) return redoLayout; + } if (startKeyguardExitAnimation) { if (DEBUG_KEYGUARD) Slog.d(TAG, "Starting keyguard exit animation"); startKeyguardExitAnimation(SystemClock.uptimeMillis()); @@ -3835,20 +3839,16 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * Updates the occluded state of the Keyguard. + * Updates the occluded state of the Keyguard immediately via + * {@link com.android.internal.policy.IKeyguardService}. * * @param isOccluded Whether the Keyguard is occluded by another window. - * @param notify Notify keyguard occlude status change immediately via - * {@link com.android.internal.policy.IKeyguardService}. * @return Whether the flags have changed and we have to redo the layout. */ - private boolean setKeyguardOccludedLw(boolean isOccluded, boolean notify) { + private boolean setKeyguardOccludedLw(boolean isOccluded) { if (DEBUG_KEYGUARD) Slog.d(TAG, "setKeyguardOccluded occluded=" + isOccluded); mKeyguardOccludedChanged = false; - if (isKeyguardOccluded() == isOccluded) { - return false; - } - mKeyguardDelegate.setOccluded(isOccluded, notify); + mKeyguardDelegate.setOccluded(isOccluded, true /* notify */); return mKeyguardDelegate.isShowing(); } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 3c4dbf247934..5d558e9c1e2f 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -170,10 +170,10 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition); /** - * @param notify {@code true} if the status change should be immediately notified via - * {@link com.android.internal.policy.IKeyguardService} + * Commit any queued changes to keyguard occlude status that had been deferred during the + * start of an animation or transition. */ - int applyKeyguardOcclusionChange(boolean notify); + int applyKeyguardOcclusionChange(); /** * Interface to the Window Manager state associated with a particular diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index d08293bf35b6..f5bc8ff02e68 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -121,6 +121,7 @@ import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENAB import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_UNSET; +import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -5205,7 +5206,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A Slog.w(TAG_WM, "Attempted to set visibility of non-existing app token: " + token); return; } - if (visible == mVisibleRequested && visible == mVisible + if (visible == mVisibleRequested && visible == mVisible && visible == isClientVisible() && mTransitionController.isShellTransitionsEnabled()) { // For shell transition, it is no-op if there is no state change. return; @@ -8362,6 +8363,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * requested in the config or via an ADB command. For more context see {@link * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)} + * <p> + * Note that this is the final step that can change the resolved bounds. After this method + * is called, the position of the bounds will be moved to app space as sandboxing if the + * activity has a size compat scale. */ private void updateResolvedBoundsPosition(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); @@ -8423,6 +8428,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Since bounds has changed, the configuration needs to be computed accordingly. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); + + // The position of configuration bounds were calculated in screen space because that is + // easier to resolve the relative position in parent container. However, if the activity is + // scaled, the position should follow the scale because the configuration will be sent to + // the client which is expected to be in a scaled environment. + if (mSizeCompatScale != 1f) { + final int screenPosX = resolvedBounds.left; + final int screenPosY = resolvedBounds.top; + final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX; + final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY; + offsetBounds(resolvedConfig, dx, dy); + } } void recomputeConfiguration() { @@ -9685,9 +9702,36 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - if (getParent() != null) { + if (mTransitionController.isShellTransitionsEnabled()) { + final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */, + mTransitionController, mWmService.mSyncEngine); + final Runnable executeRestart = () -> { + if (mState != RESTARTING_PROCESS || !attachedToProcess()) { + transition.abort(); + return; + } + // Request invisible so there will be a change after the activity is restarted + // to be visible. + setVisibleRequested(false); + transition.collect(this); + mTransitionController.requestStartTransition(transition, task, + null /* remoteTransition */, null /* displayChange */); + scheduleStopForRestartProcess(); + }; + if (mWmService.mSyncEngine.hasActiveSync()) { + mWmService.mSyncEngine.queueSyncSet( + () -> mTransitionController.moveToCollecting(transition), executeRestart); + } else { + mTransitionController.moveToCollecting(transition); + executeRestart.run(); + } + } else { startFreezingScreen(); + scheduleStopForRestartProcess(); } + } + + private void scheduleStopForRestartProcess() { // The process will be killed until the activity reports stopped with saved state (see // {@link ActivityTaskManagerService.activityStopped}). try { diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index f8fb76acf81e..5e066faf0e90 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -50,6 +50,8 @@ import android.provider.Settings; import android.util.Slog; import android.util.SparseArray; import android.view.RemoteAnimationAdapter; +import android.view.WindowManager; +import android.window.RemoteTransition; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; @@ -560,8 +562,9 @@ public class ActivityStartController { final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea() .getRootTask(WINDOWING_MODE_UNDEFINED, activityType); if (rootTask == null) return false; + final RemoteTransition remote = options.getRemoteTransition(); final ActivityRecord r = rootTask.topRunningActivity(); - if (r == null || r.isVisibleRequested() || !r.attachedToProcess() + if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null || !r.mActivityComponent.equals(intent.getComponent()) // Recents keeps invisible while device is locked. || r.mDisplayContent.isKeyguardLocked()) { @@ -570,13 +573,47 @@ public class ActivityStartController { mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r); final ActivityMetricsLogger.LaunchingState launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent); + final Transition transition = new Transition(WindowManager.TRANSIT_TO_FRONT, + 0 /* flags */, r.mTransitionController, mService.mWindowManager.mSyncEngine); + if (r.mTransitionController.isCollecting()) { + // Special case: we are entering recents while an existing transition is running. In + // this case, we know it's safe to "defer" the activity launch, so lets do so now so + // that it can get its own transition and thus update launcher correctly. + mService.mWindowManager.mSyncEngine.queueSyncSet( + () -> { + if (r.isAttached()) { + r.mTransitionController.moveToCollecting(transition); + } + }, + () -> { + if (r.isAttached() && transition.isCollecting()) { + startExistingRecentsIfPossibleInner(options, r, rootTask, + launchingState, remote, transition); + } + }); + } else { + r.mTransitionController.moveToCollecting(transition); + startExistingRecentsIfPossibleInner(options, r, rootTask, launchingState, remote, + transition); + } + return true; + } + + private void startExistingRecentsIfPossibleInner(ActivityOptions options, ActivityRecord r, + Task rootTask, ActivityMetricsLogger.LaunchingState launchingState, + RemoteTransition remoteTransition, Transition transition) { final Task task = r.getTask(); mService.deferWindowLayout(); try { final TransitionController controller = r.mTransitionController; if (controller.getTransitionPlayer() != null) { + controller.requestStartTransition(transition, task, remoteTransition, + null /* displayChange */); controller.collect(task); controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask)); + } else { + // The transition player might be died when executing the queued transition. + transition.abort(); } task.moveToFront("startExistingRecents"); task.mInResumeTopActivity = true; @@ -587,7 +624,6 @@ public class ActivityStartController { task.mInResumeTopActivity = false; mService.continueWindowLayout(); } - return true; } void registerRemoteAnimationForNextActivityStart(String packageName, diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 12be1d3186a1..ce29564d0b02 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1582,19 +1582,19 @@ class ActivityStarter { } } if (isTransientLaunch) { - if (forceTransientTransition) { - transitionController.collect(mLastStartActivityRecord); - transitionController.collect(mPriorAboveTask); + if (forceTransientTransition && newTransition != null) { + newTransition.collect(mLastStartActivityRecord); + newTransition.collect(mPriorAboveTask); } // `started` isn't guaranteed to be the actual relevant activity, so we must wait // until after we launched to identify the relevant activity. transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask); - if (forceTransientTransition) { + if (forceTransientTransition && newTransition != null) { final DisplayContent dc = mLastStartActivityRecord.getDisplayContent(); // update wallpaper target to TransientHide dc.mWallpaperController.adjustWallpaperWindows(); // execute transition because there is no change - transitionController.setReady(dc, true /* ready */); + newTransition.setReady(dc, true /* ready */); } } if (!userLeaving) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 555cd38806e6..992743ab8593 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1240,6 +1240,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions); + // A quick path (skip general intent/task resolving) to start recents animation if the + // recents (or home) activity is available in background. + if (opts != null && opts.getOriginalOptions().getTransientLaunch() + && isCallerRecents(Binder.getCallingUid())) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents"); + if (mActivityStartController.startExistingRecentsIfPossible( + intent, opts.getOriginalOptions())) { + return ActivityManager.START_TASK_TO_FRONT; + } + // Else follow the standard launch procedure. + } + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + Binder.restoreCallingIdentity(origId); + } + } assertPackageMatchesCallingUid(callingPackage); enforceNotIsolatedCaller("startActivityAsUser"); @@ -5699,23 +5718,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean validateIncomingUser, PendingIntentRecord originatingPendingIntent, BackgroundStartPrivileges backgroundStartPrivileges) { assertPackageMatchesCallingUid(callingPackage); - // A quick path (skip general intent/task resolving) to start recents animation if the - // recents (or home) activity is available in background. - if (options != null && options.getOriginalOptions() != null - && options.getOriginalOptions().getTransientLaunch() && isCallerRecents(uid)) { - try { - synchronized (mGlobalLock) { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents"); - if (mActivityStartController.startExistingRecentsIfPossible( - intent, options.getOriginalOptions())) { - return ActivityManager.START_TASK_TO_FRONT; - } - // Else follow the standard launch procedure. - } - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - } - } return getActivityStartController().startActivityInPackage(uid, realCallingPid, realCallingUid, callingPackage, callingFeatureId, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, options, userId, inTask, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a4d475fd928f..1344788d0314 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1228,7 +1228,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private void finishHoldScreenUpdate() { final boolean hold = mTmpHoldScreenWindow != null; if (hold && mTmpHoldScreenWindow != mHoldScreenWindow) { - mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid)); + mHoldScreenWakeLock.setWorkSource(new WorkSource(mTmpHoldScreenWindow.mSession.mUid, + mTmpHoldScreenWindow.mSession.mPackageName)); } mHoldScreenWindow = mTmpHoldScreenWindow; mTmpHoldScreenWindow = null; diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 8c5954856ed7..8f40e79d7c0f 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION; import static android.view.WindowManager.INPUT_CONSUMER_PIP; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER; @@ -296,8 +297,11 @@ final class InputMonitor { // be applied using the SurfaceControl hierarchy from the Organizer. This means // we need to make sure that these changes in crop are reflected in the input // windows, and so ensure this flag is set so that the input crop always reflects - // the surface hierarchy. - useSurfaceBoundsAsTouchRegion = true; + // the surface hierarchy. However, we only want to set this when the client did + // not already provide a touchable region, so that we don't ignore the one provided. + if (w.mTouchableInsets != TOUCHABLE_INSETS_REGION) { + useSurfaceBoundsAsTouchRegion = true; + } if (w.mAttrs.isModal()) { TaskFragment parent = w.getTaskFragment(); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 980a9418725b..ef464d299e2f 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -841,7 +841,7 @@ final class LetterboxUiController { int dividerInsets = getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets); int dividerSize = dividerWindowWidth - dividerInsets * 2; - final Rect bounds = new Rect(displayContent.getBounds()); + final Rect bounds = new Rect(displayContent.getWindowConfiguration().getAppBounds()); if (bounds.width() >= bounds.height()) { bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0); bounds.right = bounds.centerX(); @@ -1498,7 +1498,7 @@ final class LetterboxUiController { } private void inheritConfiguration(ActivityRecord firstOpaque) { - // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities + // To avoid wrong behaviour, we're not forcing a specific aspect ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 78ee6f95fdba..7b10c6372b0e 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -116,7 +116,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { private boolean mShowingAlertWindowNotificationAllowed; private boolean mClientDead = false; private float mLastReportedAnimatorScale; - private String mPackageName; + protected String mPackageName; private String mRelayoutTag; private final InsetsSourceControl.Array mDummyControls = new InsetsSourceControl.Array(); final boolean mSetsUnrestrictedKeepClearAreas; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8c6de8e6f638..68b2d0fc50d5 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3361,6 +3361,8 @@ class Task extends TaskFragment { && info.pictureInPictureParams.isLaunchIntoPip() && top.getLastParentBeforePip() != null) ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID; + info.lastParentTaskIdBeforePip = top != null && top.getLastParentBeforePip() != null + ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID; info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays; info.mTopActivityLocusId = top != null ? top.getLocusId() : null; diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 09d33fa73c73..6bc9fa4f3b46 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -458,6 +458,22 @@ class TaskFragment extends WindowContainer<WindowContainer> { && organizer.asBinder().equals(mTaskFragmentOrganizer.asBinder()); } + /** + * Returns the process of organizer if this TaskFragment is organized and the activity lives in + * a different process than the organizer. + */ + @Nullable + private WindowProcessController getOrganizerProcessIfDifferent(@Nullable ActivityRecord r) { + if ((r == null || mTaskFragmentOrganizerProcessName == null) + || (mTaskFragmentOrganizerProcessName.equals(r.processName) + && mTaskFragmentOrganizerUid == r.getUid())) { + // No organizer or the process is the same. + return null; + } + return mAtmService.getProcessController(mTaskFragmentOrganizerProcessName, + mTaskFragmentOrganizerUid); + } + void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { mAnimationParams = animationParams; } @@ -815,6 +831,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { setResumedActivity(record, reason + " - onActivityStateChanged"); mTaskSupervisor.mRecentTasks.add(record.getTask()); } + + // Update the process state for the organizer process if the activity is in a different + // process in case the organizer process may not have activity state change in its process. + final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(record); + if (hostProcess != null) { + mTaskSupervisor.onProcessActivityStateChanged(hostProcess, false /* forceBatch */); + hostProcess.updateProcessInfo(false /* updateServiceConnectionActivities */, + true /* activityChange */, true /* updateOomAdj */, + false /* addPendingTopUid */); + } } /** @@ -1942,6 +1968,11 @@ class TaskFragment extends WindowContainer<WindowContainer> { addingActivity.inHistory = true; task.onDescendantActivityAdded(taskHadActivity, activityType, addingActivity); } + + final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(addingActivity); + if (hostProcess != null) { + hostProcess.addEmbeddedActivity(addingActivity); + } } @Override @@ -2757,14 +2788,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { void removeChild(WindowContainer child, boolean removeSelfIfPossible) { super.removeChild(child); + final ActivityRecord r = child.asActivityRecord(); if (BackNavigationController.isScreenshotEnabled()) { //TODO(b/207481538) Remove once the infrastructure to support per-activity screenshot is // implemented - ActivityRecord r = child.asActivityRecord(); if (r != null) { mBackScreenshots.remove(r.mActivityComponent.flattenToString()); } } + final WindowProcessController hostProcess = getOrganizerProcessIfDifferent(r); + if (hostProcess != null) { + hostProcess.removeEmbeddedActivity(r); + } if (removeSelfIfPossible && shouldRemoveSelfOnLastChildRemoval() && !hasChild()) { removeImmediately("removeLastChild " + child); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 362e1c803e46..ba49dd0032a4 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -1481,9 +1481,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // then we have to notify KeyguardService directly. This can happen if there is // another ongoing transition when the app changes occlusion OR if the app dies or // is killed. Both of these are common during tests. - final boolean notify = !(transit == TRANSIT_KEYGUARD_OCCLUDE - || transit == TRANSIT_KEYGUARD_UNOCCLUDE); - mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(notify); + if (transit != TRANSIT_KEYGUARD_OCCLUDE && transit != TRANSIT_KEYGUARD_UNOCCLUDE) { + mController.mAtm.mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + } } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 918729d114d3..61685fc9635c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9383,30 +9383,6 @@ public class WindowManagerService extends IWindowManager.Stub mSurfaceSyncGroupController.markSyncGroupReady(syncGroupToken); } - private ArraySet<ActivityRecord> getVisibleActivityRecords(int displayId) { - ArraySet<ActivityRecord> result = new ArraySet<>(); - synchronized (mGlobalLock) { - ArraySet<ComponentName> addedActivities = new ArraySet<>(); - DisplayContent displayContent = mRoot.getDisplayContent(displayId); - if (displayContent != null) { - displayContent.forAllWindows( - (w) -> { - if (w.isVisible() - && w.isDisplayed() - && w.mActivityRecord != null - && !addedActivities.contains( - w.mActivityRecord.mActivityComponent) - && w.mActivityRecord.isVisible() - && w.isVisibleNow()) { - addedActivities.add(w.mActivityRecord.mActivityComponent); - result.add(w.mActivityRecord); - } - }, - true /* traverseTopToBottom */); - } - } - return result; - } /** * Must be called when a screenshot is taken via hardware chord. @@ -9422,14 +9398,20 @@ public class WindowManagerService extends IWindowManager.Stub throw new SecurityException("Requires STATUS_BAR_SERVICE permission"); } synchronized (mGlobalLock) { - ArraySet<ComponentName> notifiedApps = new ArraySet<>(); - ArraySet<ActivityRecord> visibleApps = getVisibleActivityRecords(displayId); - for (ActivityRecord ar : visibleApps) { - if (ar.isRegisteredForScreenCaptureCallback()) { - ar.reportScreenCaptured(); - notifiedApps.add(ar.mActivityComponent); - } + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent == null) { + return new ArrayList<>(); } + ArraySet<ComponentName> notifiedApps = new ArraySet<>(); + displayContent.forAllActivities( + (ar) -> { + if (!notifiedApps.contains(ar.mActivityComponent) && ar.isVisible() + && ar.isRegisteredForScreenCaptureCallback()) { + ar.reportScreenCaptured(); + notifiedApps.add(ar.mActivityComponent); + } + }, + true /* traverseTopToBottom */); return List.copyOf(notifiedApps); } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 834b708f3f9a..8685723a5807 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -69,6 +69,7 @@ import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -228,8 +229,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio * in another process. This is used to check if the process is currently showing anything * visible to the user. */ + private static final int REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY = 1; + /** The activity in a different process is embedded in a task created by this process. */ + private static final int REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY = 1 << 1; + + /** + * Activities that run on different processes while this process shows something in these + * activities or the appearance of the activities are controlled by this process. The value of + * map is an array of size 1 to store the kinds of remote. + */ @Nullable - private final ArrayList<ActivityRecord> mHostActivities = new ArrayList<>(); + private ArrayMap<ActivityRecord, int[]> mRemoteActivities; /** Whether our process is currently running a {@link RecentsAnimation} */ private boolean mRunningRecentsAnimation; @@ -857,7 +867,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return true; } } - if (isEmbedded()) { + if (hasEmbeddedWindow()) { return true; } } @@ -868,9 +878,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio * @return {@code true} if this process is rendering content on to a window shown by * another process. */ - private boolean isEmbedded() { - for (int i = mHostActivities.size() - 1; i >= 0; --i) { - final ActivityRecord r = mHostActivities.get(i); + private boolean hasEmbeddedWindow() { + if (mRemoteActivities == null) return false; + for (int i = mRemoteActivities.size() - 1; i >= 0; --i) { + if ((mRemoteActivities.valueAt(i)[0] & REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY) == 0) { + continue; + } + final ActivityRecord r = mRemoteActivities.keyAt(i); if (r.isInterestingToUserLocked()) { return true; } @@ -1038,15 +1052,46 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio /** Adds an activity that hosts UI drawn by the current process. */ void addHostActivity(ActivityRecord r) { - if (mHostActivities.contains(r)) { - return; - } - mHostActivities.add(r); + final int[] flags = getRemoteActivityFlags(r); + flags[0] |= REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY; } /** Removes an activity that hosts UI drawn by the current process. */ void removeHostActivity(ActivityRecord r) { - mHostActivities.remove(r); + removeRemoteActivityFlags(r, REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY); + } + + /** Adds an embedded activity in a different process to this process that organizes it. */ + void addEmbeddedActivity(ActivityRecord r) { + final int[] flags = getRemoteActivityFlags(r); + flags[0] |= REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY; + } + + /** Removes an embedded activity which was added by {@link #addEmbeddedActivity}. */ + void removeEmbeddedActivity(ActivityRecord r) { + removeRemoteActivityFlags(r, REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY); + } + + private int[] getRemoteActivityFlags(ActivityRecord r) { + if (mRemoteActivities == null) { + mRemoteActivities = new ArrayMap<>(); + } + int[] flags = mRemoteActivities.get(r); + if (flags == null) { + mRemoteActivities.put(r, flags = new int[1]); + } + return flags; + } + + private void removeRemoteActivityFlags(ActivityRecord r, int flags) { + if (mRemoteActivities == null) return; + final int index = mRemoteActivities.indexOfKey(r); + if (index < 0) return; + final int[] currentFlags = mRemoteActivities.valueAt(index); + currentFlags[0] &= ~flags; + if (currentFlags[0] == 0) { + mRemoteActivities.removeAt(index); + } } public interface ComputeOomAdjCallback { @@ -1121,6 +1166,16 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio } } } + if (mRemoteActivities != null) { + // Make this process have visible state if its organizer embeds visible activities of + // other process, so this process can be responsive for the organizer events. + for (int i = mRemoteActivities.size() - 1; i >= 0; i--) { + if ((mRemoteActivities.valueAt(i)[0] & REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY) != 0 + && mRemoteActivities.keyAt(i).isVisibleRequested()) { + stateFlags |= ACTIVITY_STATE_FLAG_IS_VISIBLE; + } + } + } stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { @@ -1795,7 +1850,21 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio pw.print(prefix); pw.print(" - "); pw.println(mActivities.get(i)); } } - + if (mRemoteActivities != null && !mRemoteActivities.isEmpty()) { + pw.print(prefix); pw.println("Remote Activities:"); + for (int i = mRemoteActivities.size() - 1; i >= 0; i--) { + pw.print(prefix); pw.print(" - "); + pw.print(mRemoteActivities.keyAt(i)); pw.print(" flags="); + final int flags = mRemoteActivities.valueAt(i)[0]; + if ((flags & REMOTE_ACTIVITY_FLAG_HOST_ACTIVITY) != 0) { + pw.print("host "); + } + if ((flags & REMOTE_ACTIVITY_FLAG_EMBEDDED_ACTIVITY) != 0) { + pw.print("embedded"); + } + pw.println(); + } + } if (mRecentTasks.size() > 0) { pw.println(prefix + "Recent Tasks:"); for (int i = 0; i < mRecentTasks.size(); i++) { diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 8f9b9495b137..a15d66300eca 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -16,6 +16,7 @@ package com.android.server.credentials; +import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS; import static android.Manifest.permission.CREDENTIAL_MANAGER_SET_ORIGIN; import static android.Manifest.permission.LAUNCH_CREDENTIAL_SELECTOR; import static android.content.Context.CREDENTIAL_SERVICE; @@ -124,7 +125,8 @@ public final class CredentialManagerService serviceInfos.forEach( info -> { services.add( - new CredentialManagerServiceImpl(this, mLock, resolvedUserId, info)); + new CredentialManagerServiceImpl(this, mLock, resolvedUserId, + info)); }); return services; } @@ -418,6 +420,7 @@ public final class CredentialManagerService // Check privileged permissions mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ORIGIN, null); } + enforcePermissionForAllowedProviders(request); final int userId = UserHandle.getCallingUserId(); final int callingUid = Binder.getCallingUid(); @@ -447,6 +450,9 @@ public final class CredentialManagerService // TODO(b/273308895): implement ICancellationSignal cancelTransport = CancellationSignal.createTransport(); + + enforcePermissionForAllowedProviders(request); + return cancelTransport; } @@ -823,6 +829,17 @@ public final class CredentialManagerService } } + private void enforcePermissionForAllowedProviders(GetCredentialRequest request) { + boolean containsAllowedProviders = request.getCredentialOptions() + .stream() + .anyMatch(option -> option.getAllowedProviders() != null + && !option.getAllowedProviders().isEmpty()); + if (containsAllowedProviders) { + mContext.enforceCallingPermission(CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS, + null); + } + } + private void enforceCallingPackage(String callingPackage, int callingUid) { int packageUid; PackageManager pm = mContext.createContextAsUser( diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 9bc5998802bd..95b0ff024bfc 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -94,7 +94,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential RemoteCredentialService remoteCredentialService) { android.credentials.GetCredentialRequest filteredRequest = filterOptions(providerInfo.getCapabilities(), - getRequestSession.mClientRequest); + getRequestSession.mClientRequest, + providerInfo.getComponentName()); if (filteredRequest != null) { Map<String, CredentialOption> beginGetOptionToCredentialOptionMap = new HashMap<>(); @@ -142,17 +143,19 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Nullable private static android.credentials.GetCredentialRequest filterOptions( List<String> providerCapabilities, - android.credentials.GetCredentialRequest clientRequest + android.credentials.GetCredentialRequest clientRequest, + ComponentName componentName ) { List<CredentialOption> filteredOptions = new ArrayList<>(); for (CredentialOption option : clientRequest.getCredentialOptions()) { - if (providerCapabilities.contains(option.getType())) { + if (providerCapabilities.contains(option.getType()) + && isProviderAllowed(option, componentName)) { Log.i(TAG, "In createProviderRequest - capability found : " + option.getType()); filteredOptions.add(option); } else { Log.i(TAG, "In createProviderRequest - capability not " - + "found : " + option.getType()); + + "found, or provider not allowed : " + option.getType()); } } if (!filteredOptions.isEmpty()) { @@ -165,6 +168,16 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential return null; } + private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) { + if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains( + componentName)) { + Log.d(TAG, "Provider allow list specified but does not contain this provider: " + + componentName.flattenToString()); + return false; + } + return true; + } + public ProviderGetSession(Context context, CredentialProviderInfo info, ProviderInternalCallback<GetCredentialResponse> callbacks, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 303de129d004..e9c50b54f5dd 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11780,22 +11780,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(canManageUsers(caller) || canQueryAdminPolicy(caller)); - // Move AccessibilityManager out of lock to prevent potential deadlock - final List<AccessibilityServiceInfo> installedServices; - long id = mInjector.binderClearCallingIdentity(); - try { - UserInfo user = getUserInfo(userId); - if (user.isManagedProfile()) { - userId = user.profileGroupId; - } - installedServices = withAccessibilityManager(userId, - AccessibilityManager::getInstalledAccessibilityServiceList); - } finally { - mInjector.binderRestoreCallingIdentity(id); - } + List<String> result = null; synchronized (getLockObject()) { - List<String> result = null; // If we have multiple profiles we return the intersection of the // permitted lists. This can happen in cases where we have a device // and profile owner. @@ -11817,9 +11804,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + } + + // If we have a permitted list add all system accessibility services. + if (result != null) { + long id = mInjector.binderClearCallingIdentity(); + try { + UserInfo user = getUserInfo(userId); + if (user.isManagedProfile()) { + userId = user.profileGroupId; + } + // Move AccessibilityManager out of {@link getLockObject} to prevent potential + // deadlock. + final List<AccessibilityServiceInfo> installedServices = + withAccessibilityManager(userId, + AccessibilityManager::getInstalledAccessibilityServiceList); - // If we have a permitted list add all system accessibility services. - if (result != null) { if (installedServices != null) { for (AccessibilityServiceInfo service : installedServices) { ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; @@ -11829,10 +11829,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + } finally { + mInjector.binderRestoreCallingIdentity(id); } - - return result; } + + return result; } @Override diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java index 8a5d3a6772a5..63e8e56d09b2 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -50,6 +50,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; 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.doReturn; import static org.mockito.Mockito.mock; @@ -1078,7 +1079,7 @@ public final class BroadcastQueueModernImplTest { eq(getUidForPackage(PACKAGE_GREEN)), anyInt(), eq(Intent.ACTION_TIME_TICK), eq(BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST), eq(BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD), - anyLong(), anyLong(), anyLong(), anyInt()), times(1)); + anyLong(), anyLong(), anyLong(), anyInt(), anyString(), anyString()), times(1)); } private Intent createPackageChangedIntent(int uid, List<String> componentNameList) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java index 2d4f5ca2f71d..bca39ae64823 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -1646,6 +1646,79 @@ public class BroadcastQueueTest { } /** + * Verify prioritized receivers work as expected with deferrable broadcast - broadcast to + * app in cached state should be deferred and the rest should be delivered as per the priority + * order. + */ + @Test + public void testPrioritized_withDeferrableBroadcasts() throws Exception { + // Legacy stack doesn't support deferral + Assume.assumeTrue(mImpl == Impl.MODERN); + + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN); + final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); + final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW); + final ProcessRecord receiverOrangeApp = makeActiveProcessRecord(PACKAGE_ORANGE); + + receiverGreenApp.setCached(true); + receiverBlueApp.setCached(true); + + final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK); + final BroadcastOptions opts = BroadcastOptions.makeBasic() + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); + final List receivers = List.of( + makeRegisteredReceiver(callerApp, 10), + makeRegisteredReceiver(receiverGreenApp, 9), + makeRegisteredReceiver(receiverBlueApp, 8), + makeRegisteredReceiver(receiverYellowApp, 8), + makeRegisteredReceiver(receiverOrangeApp, 7) + ); + enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, receivers)); + waitForIdle(); + + // Green ignored since it's in cached state + verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick); + // Blue ignored since it's in cached state + verifyScheduleRegisteredReceiver(never(), receiverBlueApp, timeTick); + + final IApplicationThread redThread = mAms.getProcessRecordLocked(PACKAGE_RED, + getUidForPackage(PACKAGE_RED)).getThread(); + final IApplicationThread yellowThread = mAms.getProcessRecordLocked(PACKAGE_YELLOW, + getUidForPackage(PACKAGE_YELLOW)).getThread(); + final IApplicationThread orangeThread = mAms.getProcessRecordLocked(PACKAGE_ORANGE, + getUidForPackage(PACKAGE_ORANGE)).getThread(); + + // Verify apps that are not in cached state will receive the broadcast in the order + // we expect. + final InOrder inOrder = inOrder(redThread, yellowThread, orangeThread); + inOrder.verify(redThread).scheduleRegisteredReceiver( + any(), argThat(filterEqualsIgnoringComponent(timeTick)), + anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), + eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + inOrder.verify(yellowThread).scheduleRegisteredReceiver( + any(), argThat(filterEqualsIgnoringComponent(timeTick)), + anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), + eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + inOrder.verify(orangeThread).scheduleRegisteredReceiver( + any(), argThat(filterEqualsIgnoringComponent(timeTick)), + anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(), + eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any()); + + // Shift blue to be active and confirm that deferred broadcast is delivered + receiverBlueApp.setCached(false); + mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_BLUE), false); + waitForIdle(); + verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick); + + // Shift green to be active and confirm that deferred broadcast is delivered + receiverGreenApp.setCached(false); + mUidObserver.onUidCachedChanged(getUidForPackage(PACKAGE_GREEN), false); + waitForIdle(); + verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick); + } + + /** * Verify that we handle replacing a pending broadcast. */ @Test diff --git a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java index 8f072380e0ca..3ad24de4cdca 100644 --- a/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/SpatializerHelperTest.java @@ -80,7 +80,9 @@ public class SpatializerHelperTest { asAdapter = mMockAudioSystem; } mSpatHelper = new SpatializerHelper(mMockAudioService, asAdapter, - false /*headTrackingEnabledByDefault*/); + true /*binauralEnabledDefault*/, + true /*transauralEnabledDefault*/, + false /*headTrackingEnabledDefault*/); } diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java index 94d30bb4440b..6d2ce7fbe68d 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -34,7 +34,9 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -65,12 +67,14 @@ import android.hardware.display.HdrConversionMode; import android.hardware.display.IDisplayManagerCallback; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; +import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.MessageQueue; import android.os.Process; +import android.os.RemoteException; import android.view.ContentRecordingSession; import android.view.Display; import android.view.DisplayCutout; @@ -1024,11 +1028,14 @@ public class DisplayManagerServiceTest { } @Test - public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() throws Exception { + public void testCreateVirtualDisplay_setContentRecordingSessionSuccess() + throws RemoteException { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); when(mMockWindowManagerInternal .setContentRecordingSession(any(ContentRecordingSession.class))) .thenReturn(true); + IMediaProjection projection = mock(IMediaProjection.class); + doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection)); final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( VIRTUAL_DISPLAY_NAME, 600, 800, 320); @@ -1042,17 +1049,19 @@ public class DisplayManagerServiceTest { DisplayManagerService.BinderService binderService = displayManager.new BinderService(); final int displayId = binderService.createVirtualDisplay(builder.build(), - mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + mMockAppToken /* callback */, projection, PACKAGE_NAME); assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY); } @Test - public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws Exception { + public void testCreateVirtualDisplay_setContentRecordingSessionFail() throws RemoteException { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); when(mMockWindowManagerInternal .setContentRecordingSession(any(ContentRecordingSession.class))) .thenReturn(false); + IMediaProjection projection = mock(IMediaProjection.class); + doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection)); final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( VIRTUAL_DISPLAY_NAME, 600, 800, 320); @@ -1066,11 +1075,96 @@ public class DisplayManagerServiceTest { DisplayManagerService.BinderService binderService = displayManager.new BinderService(); final int displayId = binderService.createVirtualDisplay(builder.build(), - mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + mMockAppToken /* callback */, projection, PACKAGE_NAME); assertThat(displayId).isEqualTo(Display.INVALID_DISPLAY); } + @Test + public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noFlags() { + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + // Set no flags for the VirtualDisplay. + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, 600, 800, 320); + builder.setUniqueId("uniqueId --- setContentRecordingSession false"); + builder.setContentRecordingSession( + ContentRecordingSession.createDisplaySession(new Binder(""))); + + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.windowManagerAndInputReady(); + + // Pass in a null projection. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + // VirtualDisplay is created but not for mirroring. + assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession( + any(ContentRecordingSession.class)); + } + + @Test + public void testCreateVirtualDisplay_setContentRecordingSession_noProjection_noMirroringFlag() { + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + + // Set a non-mirroring flag for the VirtualDisplay. + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, 600, 800, 320); + builder.setUniqueId("uniqueId --- setContentRecordingSession false"); + builder.setFlags(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY); + builder.setContentRecordingSession( + ContentRecordingSession.createDisplaySession(new Binder(""))); + + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.windowManagerAndInputReady(); + + // Pass in a null projection. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, null /* projection */, PACKAGE_NAME); + + // VirtualDisplay is created but not for mirroring. + assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY); + verify(mMockWindowManagerInternal, never()).setContentRecordingSession( + any(ContentRecordingSession.class)); + } + + @Test + public void testCreateVirtualDisplay_setContentRecordingSession_projection_noMirroringFlag() + throws RemoteException { + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + when(mMockWindowManagerInternal + .setContentRecordingSession(any(ContentRecordingSession.class))) + .thenReturn(true); + IMediaProjection projection = mock(IMediaProjection.class); + doReturn(true).when(mMockProjectionService).isCurrentProjection(eq(projection)); + + // Set no flags for the VirtualDisplay. + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, 600, 800, 320); + builder.setUniqueId("uniqueId --- setContentRecordingSession false"); + builder.setContentRecordingSession( + ContentRecordingSession.createDisplaySession(new Binder(""))); + + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + displayManager.windowManagerAndInputReady(); + + // Pass in a non-null projection. + DisplayManagerService.BinderService binderService = displayManager.new BinderService(); + final int displayId = binderService.createVirtualDisplay(builder.build(), + mMockAppToken /* callback */, projection, PACKAGE_NAME); + + // VirtualDisplay is created for mirroring. + assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY); + verify(mMockWindowManagerInternal, atLeastOnce()).setContentRecordingSession( + any(ContentRecordingSession.class)); + } + /** * Tests that the virtual display is created with * {@link VirtualDisplayConfig.Builder#setSurface(Surface)} diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 1b02799e1da4..db5a46976748 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -2300,7 +2300,7 @@ public class DisplayModeDirectorTest { // We don't expect any interaction with DeviceConfig when the director is initialized // because we explicitly avoid doing this as this can lead to a latency spike in the // startup of DisplayManagerService - // Verify all the loaded values are from DisplayDeviceConfig + // Verify all the loaded values are from config.xml assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 45, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 75, 0.0); @@ -2332,6 +2332,7 @@ public class DisplayModeDirectorTest { when(displayDeviceConfig.getDefaultRefreshRateInHbmSunlight()).thenReturn(75); director.defaultDisplayDeviceUpdated(displayDeviceConfig); + // Verify the new values are from the freshly loaded DisplayDeviceConfig. assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65, 0.0); @@ -2362,6 +2363,7 @@ public class DisplayModeDirectorTest { // Need to wait for the property change to propagate to the main thread. waitForIdleSync(); + // Verify the values are loaded from the DeviceConfig. assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 60, 0.0); @@ -2377,6 +2379,35 @@ public class DisplayModeDirectorTest { new int[]{20}); assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 70); assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 80); + + // Reset the DeviceConfig + config.setDefaultPeakRefreshRate(null); + config.setRefreshRateInHighZone(null); + config.setRefreshRateInLowZone(null); + config.setLowAmbientBrightnessThresholds(new int[]{}); + config.setLowDisplayBrightnessThresholds(new int[]{}); + config.setHighDisplayBrightnessThresholds(new int[]{}); + config.setHighAmbientBrightnessThresholds(new int[]{}); + config.setRefreshRateInHbmHdr(null); + config.setRefreshRateInHbmSunlight(null); + waitForIdleSync(); + + // verify the new values now fallback to DisplayDeviceConfig + assertEquals(director.getSettingsObserver().getDefaultRefreshRate(), 60, 0.0); + assertEquals(director.getSettingsObserver().getDefaultPeakRefreshRate(), 65, + 0.0); + assertEquals(director.getBrightnessObserver().getRefreshRateInHighZone(), 55); + assertEquals(director.getBrightnessObserver().getRefreshRateInLowZone(), 50); + assertArrayEquals(director.getBrightnessObserver().getHighDisplayBrightnessThreshold(), + new int[]{210}); + assertArrayEquals(director.getBrightnessObserver().getHighAmbientBrightnessThreshold(), + new int[]{2100}); + assertArrayEquals(director.getBrightnessObserver().getLowDisplayBrightnessThreshold(), + new int[]{25}); + assertArrayEquals(director.getBrightnessObserver().getLowAmbientBrightnessThreshold(), + new int[]{30}); + assertEquals(director.getHbmObserver().getRefreshRateInHbmHdr(), 65); + assertEquals(director.getHbmObserver().getRefreshRateInHbmSunlight(), 75); } @Test @@ -2536,18 +2567,18 @@ public class DisplayModeDirectorTest { super.addOnPropertiesChangedListener(namespace, executor, listener); } - void setRefreshRateInLowZone(int fps) { + void setRefreshRateInLowZone(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_LOW_ZONE, String.valueOf(fps)); } - void setRefreshRateInHbmSunlight(int fps) { + void setRefreshRateInHbmSunlight(Integer fps) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps)); } - void setRefreshRateInHbmHdr(int fps) { + void setRefreshRateInHbmHdr(Integer fps) { putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps)); } @@ -2583,13 +2614,13 @@ public class DisplayModeDirectorTest { thresholds); } - void setRefreshRateInHighZone(int fps) { + void setRefreshRateInHighZone(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_REFRESH_RATE_IN_HIGH_ZONE, String.valueOf(fps)); } - void setDefaultPeakRefreshRate(int fps) { + void setDefaultPeakRefreshRate(Integer fps) { putPropertyAndNotify( DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_PEAK_REFRESH_RATE_DEFAULT, String.valueOf(fps)); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 86878c5384fd..848790381984 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -232,7 +232,6 @@ public class AppStandbyControllerTests { long mElapsedRealtime; boolean mIsAppIdleEnabled = true; boolean mIsCharging; - boolean mIsRestrictedBucketEnabled = true; List<String> mNonIdleWhitelistApps = new ArrayList<>(); boolean mDisplayOn; DisplayManager.DisplayListener mDisplayListener; @@ -316,11 +315,6 @@ public class AppStandbyControllerTests { } @Override - boolean isRestrictedBucketEnabled() { - return mIsRestrictedBucketEnabled; - } - - @Override File getDataSystemDirectory() { return new File(getContext().getFilesDir(), Long.toString(sRandom.nextLong())); } @@ -1355,50 +1349,6 @@ public class AppStandbyControllerTests { @Test @FlakyTest(bugId = 185169504) - public void testRestrictedBucketDisabled() throws Exception { - mInjector.mIsRestrictedBucketEnabled = false; - // Get the controller to read the new value. Capturing the ContentObserver isn't possible - // at the moment. - mController.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); - - reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; - - // Nothing should be able to put it into the RESTRICTED bucket. - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, - REASON_MAIN_TIMEOUT); - assertNotBucket(STANDBY_BUCKET_RESTRICTED); - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, - REASON_MAIN_PREDICTED); - assertNotBucket(STANDBY_BUCKET_RESTRICTED); - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, - REASON_MAIN_FORCED_BY_SYSTEM); - assertNotBucket(STANDBY_BUCKET_RESTRICTED); - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, - REASON_MAIN_FORCED_BY_USER); - assertNotBucket(STANDBY_BUCKET_RESTRICTED); - } - - @Test - @FlakyTest(bugId = 185169504) - public void testRestrictedBucket_EnabledToDisabled() throws Exception { - reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); - mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD; - mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED, - REASON_MAIN_FORCED_BY_SYSTEM); - assertBucket(STANDBY_BUCKET_RESTRICTED); - - mInjector.mIsRestrictedBucketEnabled = false; - // Get the controller to read the new value. Capturing the ContentObserver isn't possible - // at the moment. - mController.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); - - mController.checkIdleStates(USER_ID); - assertNotBucket(STANDBY_BUCKET_RESTRICTED); - } - - @Test - @FlakyTest(bugId = 185169504) public void testPredictionRaiseFromRestrictedTimeout_highBucket() throws Exception { reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 91d4f8f63f38..d73a3b8e44a6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -15,9 +15,11 @@ */ package com.android.server.notification; -import static org.hamcrest.Matchers.contains; +import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS; + +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; @@ -43,13 +45,14 @@ import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; import android.test.suitebuilder.annotation.SmallTest; -import androidx.test.runner.AndroidJUnit4; - +import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags; import com.android.server.UiServiceTestCase; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -58,7 +61,7 @@ import java.util.Collections; import java.util.List; @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) public class NotificationComparatorTest extends UiServiceTestCase { @Mock Context mContext; @Mock TelecomManager mTm; @@ -92,9 +95,24 @@ public class NotificationComparatorTest extends UiServiceTestCase { private NotificationRecord mRecordColorized; private NotificationRecord mRecordColorizedCall; + @Parameterized.Parameters(name = "sortByInterruptiveness={0}") + public static Boolean[] getSortByInterruptiveness() { + return new Boolean[] { true, false }; + } + + @Parameterized.Parameter + public boolean mSortByInterruptiveness; + @Before public void setUp() { MockitoAnnotations.initMocks(this); + SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> { + if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) { + return !mSortByInterruptiveness; + } + return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag); + }; + int userId = UserHandle.myUserId(); when(mContext.getResources()).thenReturn(getContext().getResources()); @@ -126,7 +144,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { new StatusBarNotification(callPkg, callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid, nonInterruptiveNotif, - new UserHandle(userId), "", 2000), getDefaultChannel()); + new UserHandle(userId), "", 2001), getDefaultChannel()); mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN); mRecordMinCallNonInterruptive.setInterruptive(false); @@ -228,7 +246,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { .setColorized(true).setColor(Color.WHITE) .build(); mRecordCheaterColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, 1, "cheater", uid2, uid2, n11, new UserHandle(userId), + pkg2, 1, "cheaterColorized", uid2, uid2, n11, new UserHandle(userId), "", 9258), getDefaultChannel()); mRecordCheaterColorized.setSystemImportance(NotificationManager.IMPORTANCE_LOW); @@ -262,6 +280,11 @@ public class NotificationComparatorTest extends UiServiceTestCase { mRecordColorizedCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); } + @After + public void tearDown() { + SystemUiSystemPropertiesFlags.TEST_RESOLVER = null; + } + @Test public void testOrdering() { final List<NotificationRecord> expected = new ArrayList<>(); @@ -281,8 +304,13 @@ public class NotificationComparatorTest extends UiServiceTestCase { expected.add(mNoMediaSessionMedia); expected.add(mRecordCheater); expected.add(mRecordCheaterColorized); - expected.add(mRecordMinCall); - expected.add(mRecordMinCallNonInterruptive); + if (mSortByInterruptiveness) { + expected.add(mRecordMinCall); + expected.add(mRecordMinCallNonInterruptive); + } else { + expected.add(mRecordMinCallNonInterruptive); + expected.add(mRecordMinCall); + } List<NotificationRecord> actual = new ArrayList<>(); actual.addAll(expected); @@ -290,14 +318,18 @@ public class NotificationComparatorTest extends UiServiceTestCase { Collections.sort(actual, new NotificationComparator(mContext)); - assertThat(actual, contains(expected.toArray())); + assertThat(actual).containsExactlyElementsIn(expected).inOrder(); } @Test public void testRankingScoreOverrides() { NotificationComparator comp = new NotificationComparator(mContext); NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive); - assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0); + if (mSortByInterruptiveness) { + assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0); + } else { + assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0); + } when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f); assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 39060cbe9bf7..02c030d16f6a 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -5841,6 +5841,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testVisualDifference_sameImages() { + Icon large = Icon.createWithResource(mContext, 1); + Notification n1 = new Notification.Builder(mContext, "channel") + .setSmallIcon(1).setLargeIcon(large).build(); + Notification n2 = new Notification.Builder(mContext, "channel") + .setSmallIcon(1).setLargeIcon(large).build(); + + NotificationRecord r1 = notificationToRecord(n1); + NotificationRecord r2 = notificationToRecord(n2); + + assertThat(mService.isVisuallyInterruptive(r1, r2)).isFalse(); + } + + @Test + public void testVisualDifference_differentSmallImage() { + Icon large = Icon.createWithResource(mContext, 1); + Notification n1 = new Notification.Builder(mContext, "channel") + .setSmallIcon(1).setLargeIcon(large).build(); + Notification n2 = new Notification.Builder(mContext, "channel") + .setSmallIcon(2).setLargeIcon(large).build(); + + NotificationRecord r1 = notificationToRecord(n1); + NotificationRecord r2 = notificationToRecord(n2); + + assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue(); + } + + @Test + public void testVisualDifference_differentLargeImage() { + Icon large1 = Icon.createWithResource(mContext, 1); + Icon large2 = Icon.createWithResource(mContext, 2); + Notification n1 = new Notification.Builder(mContext, "channel") + .setSmallIcon(1).setLargeIcon(large1).build(); + Notification n2 = new Notification.Builder(mContext, "channel") + .setSmallIcon(1).setLargeIcon(large2).build(); + + NotificationRecord r1 = notificationToRecord(n1); + NotificationRecord r2 = notificationToRecord(n2); + + assertThat(mService.isVisuallyInterruptive(r1, r2)).isTrue(); + } + + private NotificationRecord notificationToRecord(Notification n) { + return new NotificationRecord( + mContext, + new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, n, + UserHandle.getUserHandleForUid(mUid), null, 0), + mock(NotificationChannel.class)); + } + + @Test public void testHideAndUnhideNotificationsOnSuspendedPackageBroadcast() { // post 2 notification from this package final NotificationRecord notif1 = generateNotificationRecord( diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 2cc46a9c587d..adc3db73a172 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -108,7 +108,6 @@ import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -604,11 +603,11 @@ public class SizeCompatTests extends WindowTestsBase { // The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100. final float scale = (float) display.mBaseDisplayHeight / currentBounds.height(); final int offsetX = (int) (display.mBaseDisplayWidth - (origBounds.width() * scale)) / 2; - assertEquals(offsetX, currentBounds.left); + final int screenX = mActivity.getBounds().left; + assertEquals(offsetX, screenX); - // The position of configuration bounds should be the same as compat bounds. - assertEquals(mActivity.getBounds().left, currentBounds.left); - assertEquals(mActivity.getBounds().top, currentBounds.top); + // The position of configuration bounds should be in app space. + assertEquals(screenX, (int) (currentBounds.left * scale + 0.5f)); // Activity is sandboxed to the offset size compat bounds. assertActivityMaxBoundsSandboxed(); @@ -638,7 +637,7 @@ public class SizeCompatTests extends WindowTestsBase { // The size should still be in portrait [100, 0 - 1100, 2500] = 1000x2500. assertEquals(origBounds.width(), currentBounds.width()); assertEquals(origBounds.height(), currentBounds.height()); - assertEquals(offsetX, currentBounds.left); + assertEquals(offsetX, mActivity.getBounds().left); assertScaled(); // Activity is sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); @@ -801,9 +800,11 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origAppBounds.height(), appBounds.height()); // The activity is 1000x1400 and the display is 2500x1000. assertScaled(); - // The position in configuration should be global coordinates. - assertEquals(mActivity.getBounds().left, currentBounds.left); - assertEquals(mActivity.getBounds().top, currentBounds.top); + final float scale = mActivity.getCompatScale(); + // The position in configuration should be in app coordinates. + final Rect screenBounds = mActivity.getBounds(); + assertEquals(screenBounds.left, (int) (currentBounds.left * scale + 0.5f)); + assertEquals(screenBounds.top, (int) (currentBounds.top * scale + 0.5f)); // Activity max bounds are sandboxed due to size compat mode. assertActivityMaxBoundsSandboxed(); @@ -2025,7 +2026,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2050,7 +2051,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2076,7 +2077,7 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); } @Test @@ -2102,7 +2103,89 @@ public class SizeCompatTests extends WindowTestsBase { float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth); final Rect afterBounds = activity.getBounds(); final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); - Assert.assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) + public void testOverrideSplitScreenAspectRatio_splitScreenActivityInPortrait_notLetterboxed() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenWidth = 1800; + final int screenHeight = 1000; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Simulate real display with top insets. + final int topInset = 30; + activity.mDisplayContent.getWindowConfiguration() + .setAppBounds(0, topInset, screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, activity.getDisplayContent()); + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); + + // Unresizable portrait-only activity. + prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_PORTRAIT); + + // Activity should have the aspect ratio of a split screen activity and occupy exactly one + // half of the screen, so there is no letterbox + float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth); + final Rect afterBounds = activity.getBounds(); + final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width(); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertFalse(activity.areBoundsLetterboxed()); + } + + @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN}) + public void testOverrideSplitScreenAspectRatio_splitScreenActivityInLandscape_notLetterboxed() { + mAtm.mDevEnableNonResizableMultiWindow = true; + final int screenWidth = 1000; + final int screenHeight = 1800; + setUpDisplaySizeWithApp(screenWidth, screenHeight); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setTask(mTask) + .setComponent(ComponentName.createRelative(mContext, + SizeCompatTests.class.getName())) + .setUid(android.os.Process.myUid()) + .build(); + + activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Simulate real display with top insets. + final int leftInset = 30; + activity.mDisplayContent.getWindowConfiguration() + .setAppBounds(leftInset, 0, screenWidth, screenHeight); + + final TestSplitOrganizer organizer = + new TestSplitOrganizer(mAtm, activity.getDisplayContent()); + // Move activity to split screen which takes half of the screen. + mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test"); + organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight)); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode()); + assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode()); + + // Unresizable landscape-only activity. + prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_LANDSCAPE); + + // Activity should have the aspect ratio of a split screen activity and occupy exactly one + // half of the screen, so there is no letterbox + float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight); + final Rect afterBounds = activity.getBounds(); + final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height(); + assertEquals(expectedAspectRatio, afterAspectRatio, 0.001f); + assertFalse(activity.areBoundsLetterboxed()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 378e8bed3524..99ab715ab987 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -398,6 +398,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { public void testOnActivityReparentedToTask_activityNotInOrganizerProcess_useTemporaryToken() { final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); + final WindowProcessController organizerProc = mSystemServicesTestRule.addProcess( + "pkg.organizer", DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME, pid, uid); mTaskFragment.setTaskFragmentOrganizer(mOrganizer.getOrganizerToken(), uid, DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); @@ -436,6 +438,15 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // The temporary token can only be used once. assertNull(mController.getReparentActivityFromTemporaryToken(mIOrganizer, change.getActivityToken())); + + // The organizer process should also have visible state by the visible activity in a + // different process. + activity.setVisibleRequested(true); + activity.setState(ActivityRecord.State.RESUMED, "test"); + assertTrue(organizerProc.hasVisibleActivities()); + activity.setVisibleRequested(false); + activity.setState(ActivityRecord.State.STOPPED, "test"); + assertFalse(organizerProc.hasVisibleActivities()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 1d0715ab0460..2a2641edb637 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -314,7 +314,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public int applyKeyguardOcclusionChange(boolean notify) { + public int applyKeyguardOcclusionChange() { return 0; } diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java index 7c79447399b2..e8a8576daf58 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -17,6 +17,7 @@ package android.telephony.satellite; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -33,6 +34,7 @@ public final class PointingInfo implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) { mSatelliteAzimuthDegrees = satelliteAzimuthDegrees; mSatelliteElevationDegrees = satelliteElevationDegrees; diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index df80159780ec..87c8db317195 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -17,6 +17,7 @@ package android.telephony.satellite; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -45,6 +46,7 @@ public final class SatelliteCapabilities implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isPointingRequired, int maxBytesPerOutgoingDatagram) { mSupportedRadioTechnologies = supportedRadioTechnologies == null diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java index d3cb8a07e4ba..44f56f3d315c 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java @@ -17,6 +17,7 @@ package android.telephony.satellite; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -32,6 +33,7 @@ public final class SatelliteDatagram implements Parcelable { /** * @hide */ + @UnsupportedAppUsage public SatelliteDatagram(@NonNull byte[] data) { mData = data; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index f237ada9d1e0..d8a6fafcdd2f 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -17,6 +17,7 @@ package android.telephony.satellite; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; import com.android.internal.telephony.ILongConsumer; @@ -36,6 +37,7 @@ public interface SatelliteDatagramCallback { * datagramId to Telephony. If the callback is not received within five minutes, * Telephony will resend the datagram. */ + @UnsupportedAppUsage void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram, int pendingCount, @NonNull ILongConsumer callback); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index e32566dc470f..7d82fd8d6d69 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; +import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -82,6 +83,7 @@ public class SatelliteManager { * @param context The context the SatelliteManager belongs to. * @hide */ + @UnsupportedAppUsage public SatelliteManager(@Nullable Context context) { this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); } @@ -127,6 +129,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_ENABLED = "satellite_enabled"; /** @@ -134,6 +137,7 @@ public class SatelliteManager { * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled"; /** @@ -141,6 +145,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_SUPPORTED = "satellite_supported"; /** @@ -148,6 +153,7 @@ public class SatelliteManager { * {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_CAPABILITIES = "satellite_capabilities"; /** @@ -155,6 +161,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned"; /** @@ -162,6 +169,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED = "satellite_communication_allowed"; @@ -170,6 +178,7 @@ public class SatelliteManager { * {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}. * @hide */ + @UnsupportedAppUsage public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** @@ -340,6 +349,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, @NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { @@ -382,6 +392,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -436,6 +447,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -488,6 +500,7 @@ public class SatelliteManager { * * @throws IllegalStateException if the Telephony process is not currently available. */ + @UnsupportedAppUsage public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -541,6 +554,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -707,6 +721,7 @@ public class SatelliteManager { */ public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; + /** @hide */ @IntDef(prefix = "DATAGRAM_TYPE_", value = { DATAGRAM_TYPE_UNKNOWN, DATAGRAM_TYPE_SOS_MESSAGE, @@ -732,6 +747,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener, @NonNull SatelliteTransmissionUpdateCallback callback) { @@ -801,6 +817,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void stopSatelliteTransmissionUpdates( @NonNull SatelliteTransmissionUpdateCallback callback, @NonNull @CallbackExecutor Executor executor, @@ -856,6 +873,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void provisionSatelliteService(@NonNull String token, @NonNull String regionId, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @@ -895,7 +913,7 @@ public class SatelliteManager { * {@link SatelliteProvisionStateCallback#onSatelliteProvisionStateChanged(boolean)} * should report as deprovisioned. * For provisioning satellite service, refer to - * {@link #provisionSatelliteService(String, CancellationSignal, Executor, Consumer)}. + * {@link #provisionSatelliteService(String, String, CancellationSignal, Executor, Consumer)} * * @param token The token of the device/subscription to be deprovisioned. * @param resultListener Listener for the {@link SatelliteError} result of the operation. @@ -904,6 +922,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void deprovisionSatelliteService(@NonNull String token, @NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { @@ -943,6 +962,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage @SatelliteError public int registerForSatelliteProvisionStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteProvisionStateCallback callback) { @@ -985,6 +1005,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void unregisterForSatelliteProvisionStateChanged( @NonNull SatelliteProvisionStateCallback callback) { Objects.requireNonNull(callback); @@ -1023,6 +1044,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1074,6 +1096,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage @SatelliteError public int registerForSatelliteModemStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteStateCallback callback) { @@ -1113,6 +1136,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) { Objects.requireNonNull(callback); ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback); @@ -1147,6 +1171,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage @SatelliteError public int registerForSatelliteDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDatagramCallback callback) { @@ -1190,6 +1215,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) { Objects.requireNonNull(callback); ISatelliteDatagramCallback internalCallback = @@ -1227,6 +1253,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); @@ -1279,6 +1306,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void sendSatelliteDatagram(@DatagramType int datagramType, @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, @NonNull @CallbackExecutor Executor executor, @@ -1324,6 +1352,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestIsSatelliteCommunicationAllowedForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { @@ -1381,6 +1410,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) + @UnsupportedAppUsage public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Duration, SatelliteException> callback) { Objects.requireNonNull(executor); diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index a62eb8b8a5fb..20875af94f5a 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -16,6 +16,8 @@ package android.telephony.satellite; +import android.compat.annotation.UnsupportedAppUsage; + /** * A callback class for monitoring satellite provision state change events. * @@ -28,5 +30,6 @@ public interface SatelliteProvisionStateCallback { * @param provisioned The new provision state. {@code true} means satellite is provisioned * {@code false} means satellite is not provisioned. */ + @UnsupportedAppUsage void onSatelliteProvisionStateChanged(boolean provisioned); } diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java index d9ecaa3467e3..3312e3a90281 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,6 +16,8 @@ package android.telephony.satellite; +import android.compat.annotation.UnsupportedAppUsage; + /** * A callback class for monitoring satellite modem state change events. * @@ -26,5 +28,6 @@ public interface SatelliteStateCallback { * Called when satellite modem state changes. * @param state The new satellite modem state. */ + @UnsupportedAppUsage void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index d4fe57a0be2e..17fff4480679 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -17,6 +17,7 @@ package android.telephony.satellite; import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; /** * A callback class for monitoring satellite position update and datagram transfer state change @@ -30,6 +31,7 @@ public interface SatelliteTransmissionUpdateCallback { * * @param pointingInfo The pointing info containing the satellite location. */ + @UnsupportedAppUsage void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** @@ -39,6 +41,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param sendPendingCount The number of datagrams that are currently being sent. * @param errorCode If datagram transfer failed, the reason for failure. */ + @UnsupportedAppUsage void onSendDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, @SatelliteManager.SatelliteError int errorCode); @@ -50,6 +53,7 @@ public interface SatelliteTransmissionUpdateCallback { * @param receivePendingCount The number of datagrams that are currently pending to be received. * @param errorCode If datagram transfer failed, the reason for failure. */ + @UnsupportedAppUsage void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteError int errorCode); diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 5dc2dd7d93a8..47b2cda62f51 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest @@ -40,20 +39,27 @@ import org.junit.runners.Parameterized * Launch an app [testApp] and wait animation to complete * Press back button * ``` + * * To run only the presubmit assertions add: `-- + * * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` * ``` + * * To run only the postsubmit assertions add: `-- + * * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` * ``` + * * To run only the flaky assertions add: `-- + * * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -65,7 +71,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt index 9fa840190fbf..70eedd93e1ba 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -25,7 +24,6 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index b042a14b30da..d8abb4e8b343 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest @@ -40,20 +39,27 @@ import org.junit.runners.Parameterized * Launch an app [testApp] and wait animation to complete * Press home button * ``` + * * To run only the presubmit assertions add: `-- + * * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Presubmit` * ``` + * * To run only the postsubmit assertions add: `-- + * * ``` * --module-arg FlickerTests:exclude-annotation:androidx.test.filters.FlakyTest * --module-arg FlickerTests:include-annotation:android.platform.test.annotations.Postsubmit` * ``` + * * To run only the flaky assertions add: `-- + * * ``` * --module-arg FlickerTests:include-annotation:androidx.test.filters.FlakyTest` * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -65,7 +71,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt index 136995a78fd7..c74f54be61c9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -25,7 +24,6 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt index 3289bc601160..ac05c7687311 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTestCfArm.kt @@ -41,4 +41,4 @@ class OpenAppAfterCameraTestCfArm(flicker: FlickerTest) : OpenAppAfterCameraTest return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt index ccbe74f04a70..09c17b17a3c5 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIconCfArm.kt @@ -18,7 +18,6 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.tools.common.NavBar -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -29,7 +28,6 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** Some assertions will fail because of b/264415996 */ -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt index d0dc42f29b9e..5cacb04e4b16 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTest.kt @@ -18,14 +18,13 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -42,6 +41,7 @@ import org.junit.runners.Parameterized * Make sure no apps are running on the device * Launch an app [testApp] and wait animation to complete * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -53,7 +53,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt index f75d9eede25b..f77f96874fb9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdTestCfArm.kt @@ -17,7 +17,6 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -27,7 +26,6 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -49,4 +47,4 @@ class OpenAppColdTestCfArm(flicker: FlickerTest) : OpenAppColdTest(flicker) { return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt index 4aa78d4482fb..8b4a613305c0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationColdCfArm.kt @@ -44,4 +44,4 @@ class OpenAppFromNotificationColdCfArm(flicker: FlickerTest) : return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt new file mode 100644 index 000000000000..d90b3ca75636 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarmCfArm.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.launch + +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerTest +import android.tools.device.flicker.legacy.FlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenAppFromNotificationWarmCfArm(flicker: FlickerTest) : + OpenAppFromNotificationWarm(flicker) { + companion object { + /** + * Creates the test configurations. + * + * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return FlickerTestFactory.nonRotationTests() + } + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 00d7544f7217..66e0f0657064 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -19,7 +19,6 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest @@ -44,6 +43,7 @@ import org.junit.runners.Parameterized * Relaunch an app [testApp] by selecting it in the overview screen, and wait animation to * complete (only this action is traced) * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -55,7 +55,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt index ff24190a7aef..8139e1f5f507 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -26,7 +25,6 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** Some assertions will fail because of b/264415996 */ -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt index 9ab61566e13f..14df84e26ac4 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppNonResizeableTest.kt @@ -22,7 +22,6 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.common.datatypes.component.ComponentNameMatcher -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -48,6 +47,7 @@ import org.junit.runners.Parameterized * Lock the device. * Launch an app on top of the lock screen [testApp] and wait animation to complete * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -59,7 +59,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt index cdd2d45769bb..cfc8e4672ddf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTest.kt @@ -18,7 +18,6 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest @@ -42,6 +41,7 @@ import org.junit.runners.Parameterized * Press home * Relaunch an app [testApp] and wait animation to complete (only this action is traced) * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) @@ -53,7 +53,6 @@ import org.junit.runners.Parameterized * ``` */ @RequiresDevice -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt index 9679059e5069..b47c9319aced 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppWarmTestCfArm.kt @@ -16,7 +16,6 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory @@ -25,7 +24,6 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -@FlickerServiceCompatible @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -43,4 +41,4 @@ class OpenAppWarmTestCfArm(flicker: FlickerTest) : OpenAppWarmTest(flicker) { return FlickerTestFactory.nonRotationTests() } } -}
\ No newline at end of file +} diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt index 786bb32096ad..e876e5777145 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraOnDoubleClickPowerButton.kt @@ -24,10 +24,10 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import android.view.KeyEvent import androidx.test.filters.RequiresDevice import com.android.server.wm.flicker.helpers.setRotation -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -44,6 +44,7 @@ import org.junit.runners.Parameterized * Make sure no apps are running on the device * Launch an app [testApp] and wait animation to complete * ``` + * * Notes: * ``` * 1. Some default assertions (e.g., nav bar, status bar and screen covered) diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt index b848e63c9c87..6cbb975b34a9 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OverrideTaskTransitionTest.kt @@ -27,13 +27,13 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.FlickerTest import android.tools.device.flicker.legacy.FlickerTestFactory +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.helpers.wakeUpAndGoToHomeScreen import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.R -import com.android.server.wm.flicker.helpers.setRotation -import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule -import android.tools.device.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.setRotation import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/wifi/TEST_MAPPING b/wifi/TEST_MAPPING index 94e4f4d6bf13..14f5af3fd6bc 100644 --- a/wifi/TEST_MAPPING +++ b/wifi/TEST_MAPPING @@ -3,5 +3,15 @@ { "name": "FrameworksWifiNonUpdatableApiTests" } + ], + "presubmit-large": [ + { + "name": "CtsWifiTestCases", + "options": [ + { + "exclude-annotation": "android.net.wifi.cts.VirtualDeviceNotSupported" + } + ] + } ] } |