summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/aconfig/Android.bp39
-rw-r--r--apct-tests/perftests/aconfig/AndroidManifest.xml27
-rw-r--r--apct-tests/perftests/aconfig/AndroidTest.xml63
-rw-r--r--apct-tests/perftests/aconfig/OWNERS1
-rw-r--r--apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java139
-rw-r--r--apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java8
-rw-r--r--apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java214
-rw-r--r--apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java17
-rw-r--r--apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java82
-rw-r--r--core/java/android/app/Notification.java21
-rw-r--r--core/java/android/view/NotificationHeaderView.java32
-rw-r--r--core/java/android/view/ViewRootImpl.java59
-rw-r--r--core/java/com/android/internal/widget/NotificationExpandButton.java78
-rw-r--r--core/res/res/drawable/ic_accessibility_hearing_aid.xml4
-rw-r--r--core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml27
-rw-r--r--core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml26
-rw-r--r--core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml26
-rw-r--r--core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml27
-rw-r--r--core/res/res/values/dimens.xml3
-rw-r--r--core/res/res/values/strings.xml12
-rw-r--r--core/res/res/values/symbols.xml10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java14
-rw-r--r--native/android/performance_hint.cpp9
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java2
-rw-r--r--packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt14
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java2
-rw-r--r--packages/SystemUI/res/layout/accessibility_floating_menu_item.xml52
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt111
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java28
-rw-r--r--services/core/java/com/android/server/accounts/AccountManagerService.java6
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java2
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java45
-rw-r--r--services/core/java/com/android/server/wm/PinnedTaskController.java31
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java62
-rw-r--r--tools/systemfeatures/Android.bp69
-rw-r--r--tools/systemfeatures/README.md108
-rw-r--r--tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java119
-rw-r--r--tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java123
-rw-r--r--tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt58
-rw-r--r--tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java4
-rw-r--r--tools/systemfeatures/tests/src/android/content/Context.java (renamed from tools/systemfeatures/tests/src/Context.java)0
-rw-r--r--tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java (renamed from tools/systemfeatures/tests/src/FeatureInfo.java)0
-rw-r--r--tools/systemfeatures/tests/src/android/content/pm/PackageManager.java (renamed from tools/systemfeatures/tests/src/PackageManager.java)8
-rw-r--r--tools/systemfeatures/tests/src/android/util/ArrayMap.java (renamed from tools/systemfeatures/tests/src/ArrayMap.java)0
-rw-r--r--tools/systemfeatures/tests/src/android/util/ArraySet.java (renamed from tools/systemfeatures/tests/src/ArraySet.java)0
65 files changed, 1960 insertions, 324 deletions
diff --git a/apct-tests/perftests/aconfig/Android.bp b/apct-tests/perftests/aconfig/Android.bp
new file mode 100644
index 000000000000..715923de1eb7
--- /dev/null
+++ b/apct-tests/perftests/aconfig/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_android_core_experiments",
+ // 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
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "AconfigPerfTests",
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "aconfig_device_paths_java_util",
+ "androidx.test.rules",
+ "apct-perftests-utils",
+ "collector-device-lib",
+ "truth",
+ ],
+ platform_apis: true,
+ certificate: "platform",
+ test_suites: ["device-tests"],
+ data: [":perfetto_artifacts"],
+}
diff --git a/apct-tests/perftests/aconfig/AndroidManifest.xml b/apct-tests/perftests/aconfig/AndroidManifest.xml
new file mode 100644
index 000000000000..e9d7c176303f
--- /dev/null
+++ b/apct-tests/perftests/aconfig/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.perftests.aconfig">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.perftests.aconfig"/>
+
+</manifest> \ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/AndroidTest.xml b/apct-tests/perftests/aconfig/AndroidTest.xml
new file mode 100644
index 000000000000..036e0310def2
--- /dev/null
+++ b/apct-tests/perftests/aconfig/AndroidTest.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Runs AconfigPerfTests metric instrumentation.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="AconfigPerfTests.apk" />
+ </target_preparer>
+
+ <!-- Needed for pushing the trace config file -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+ </target_preparer>
+
+
+ <!-- Needed for pulling the collected trace config on to the host -->
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="pull-pattern-keys" value="perfetto_file_path" />
+ </metrics_collector>
+
+ <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+ <option name="isolated-storage" value="false" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.perftests.aconfig" />
+ <option name="hidden-api-checks" value="false"/>
+
+ <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+ <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+ <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ <!-- ProcLoadListener related arguments -->
+ <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+ <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+ <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+ <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+ <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+ <!-- PerfettoListener related arguments -->
+ <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+ <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+ <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+ </test>
+</configuration> \ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/OWNERS b/apct-tests/perftests/aconfig/OWNERS
new file mode 100644
index 000000000000..2202076593fb
--- /dev/null
+++ b/apct-tests/perftests/aconfig/OWNERS
@@ -0,0 +1 @@
+file:platform/packages/modules/ConfigInfrastructure:/OWNERS \ No newline at end of file
diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
new file mode 100644
index 000000000000..df6e3c836256
--- /dev/null
+++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.flagging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.aconfig.DeviceProtosTestUtil;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(Parameterized.class)
+public class AconfigPackagePerfTest {
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Parameterized.Parameters(name = "isPlatform={0}")
+ public static Collection<Object[]> data() {
+ return Arrays.asList(new Object[][] {{false}, {true}});
+ }
+
+ private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product");
+ private static List<parsed_flag> sFlags;
+
+ @BeforeClass
+ public static void init() {
+ try {
+ sFlags = DeviceProtosTestUtil.loadAndParseFlagProtos();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Parameterized.Parameter(0)
+
+ // if this variable is true, then the test query flags from system/product/vendor
+ // if this variable is false, then the test query flags from updatable partitions
+ public boolean mIsPlatform;
+
+ @Test
+ public void timeAconfigPackageLoadOnePackage() {
+ String packageName = "";
+ for (parsed_flag flag : sFlags) {
+ if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ packageName = flag.package_;
+ break;
+ }
+ }
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ AconfigPackage.load(packageName);
+ }
+ }
+
+ @Test
+ public void timeAconfigPackageLoadMultiplePackages() {
+ // load num packages
+ int packageNum = 25;
+ Set<String> packageSet = new HashSet<>();
+ for (parsed_flag flag : sFlags) {
+ if (mIsPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ packageSet.add(flag.package_);
+ }
+ if (packageSet.size() >= packageNum) {
+ break;
+ }
+ }
+ List<String> packageList = new ArrayList(packageSet);
+ assertThat(packageList.size()).isAtLeast(packageNum);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ for (int i = 0; state.keepRunning(); i++) {
+ AconfigPackage.load(packageList.get(i % packageNum));
+ }
+ }
+
+ @Test
+ public void timeAconfigPackageGetBooleanFlagValue() {
+ // get one package contains num of flags
+ int flagNum = 20;
+ List<parsed_flag> l = findNumFlagsInSamePackage(flagNum, mIsPlatform);
+ List<String> flagName = new ArrayList<>();
+ String packageName = l.get(0).package_;
+ for (parsed_flag flag : l) {
+ flagName.add(flag.name);
+ }
+ assertThat(flagName.size()).isAtLeast(flagNum);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ AconfigPackage ap = AconfigPackage.load(packageName);
+ for (int i = 0; state.keepRunning(); i++) {
+ ap.getBooleanFlagValue(flagName.get(i % flagNum), false);
+ }
+ }
+
+ private static List<parsed_flag> findNumFlagsInSamePackage(int num, boolean isPlatform) {
+ Map<String, List<parsed_flag>> packageToFlag = new HashMap<>();
+ List<parsed_flag> ret = new ArrayList<parsed_flag>();
+ for (parsed_flag flag : sFlags) {
+ if (isPlatform == PLATFORM_CONTAINERS.contains(flag.container)) {
+ ret =
+ packageToFlag.computeIfAbsent(
+ flag.package_, k -> new ArrayList<parsed_flag>());
+ ret.add(flag);
+ if (ret.size() >= num) {
+ break;
+ }
+ }
+ }
+ return ret;
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java
index 1a7258a802df..4c3416523b99 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientEndpoint.java
@@ -20,6 +20,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.AutoCloseable;
import java.net.InetAddress;
import java.net.SocketException;
import java.nio.channels.ClosedChannelException;
@@ -33,7 +34,7 @@ import org.conscrypt.ChannelType;
* Client-side endpoint. Provides basic services for sending/receiving messages from the client
* socket.
*/
-final class ClientEndpoint {
+final class ClientEndpoint implements AutoCloseable {
private final SSLSocket socket;
private InputStream input;
private OutputStream output;
@@ -56,6 +57,11 @@ final class ClientEndpoint {
}
}
+ @Override
+ public void close() {
+ stop();
+ }
+
void stop() {
try {
socket.close();
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index f20b1706129b..9e45c4ae23b5 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -44,24 +44,21 @@ import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import org.junit.Rule;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import android.conscrypt.ServerEndpoint.MessageProcessor;
-/**
- * Benchmark for comparing performance of server socket implementations.
- */
+/** Benchmark for comparing performance of server socket implementations. */
@RunWith(JUnitParamsRunner.class)
@LargeTest
public final class ClientSocketPerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- /**
- * Provider for the test configuration
- */
+ /** Provider for the test configuration */
private class Config {
EndpointFactory a_clientFactory;
EndpointFactory b_serverFactory;
@@ -69,19 +66,22 @@ public final class ClientSocketPerfTest {
String d_cipher;
ChannelType e_channelType;
PerfTestProtocol f_protocol;
- Config(EndpointFactory clientFactory,
- EndpointFactory serverFactory,
- int messageSize,
- String cipher,
- ChannelType channelType,
- PerfTestProtocol protocol) {
- a_clientFactory = clientFactory;
- b_serverFactory = serverFactory;
- c_messageSize = messageSize;
- d_cipher = cipher;
- e_channelType = channelType;
- f_protocol = protocol;
+
+ Config(
+ EndpointFactory clientFactory,
+ EndpointFactory serverFactory,
+ int messageSize,
+ String cipher,
+ ChannelType channelType,
+ PerfTestProtocol protocol) {
+ a_clientFactory = clientFactory;
+ b_serverFactory = serverFactory;
+ c_messageSize = messageSize;
+ d_cipher = cipher;
+ e_channelType = channelType;
+ f_protocol = protocol;
}
+
public EndpointFactory clientFactory() {
return a_clientFactory;
}
@@ -112,23 +112,43 @@ public final class ClientSocketPerfTest {
for (EndpointFactory endpointFactory : EndpointFactory.values()) {
for (ChannelType channelType : ChannelType.values()) {
for (PerfTestProtocol protocol : PerfTestProtocol.values()) {
- params.add(new Object[] {new Config(endpointFactory,
- endpointFactory, 64, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- channelType, protocol)});
- params.add(new Object[] {new Config(endpointFactory,
- endpointFactory, 512, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- channelType, protocol)});
- params.add(new Object[] {new Config(endpointFactory,
- endpointFactory, 4096, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- channelType, protocol)});
+ params.add(
+ new Object[] {
+ new Config(
+ endpointFactory,
+ endpointFactory,
+ 64,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType,
+ protocol)
+ });
+ params.add(
+ new Object[] {
+ new Config(
+ endpointFactory,
+ endpointFactory,
+ 512,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType,
+ protocol)
+ });
+ params.add(
+ new Object[] {
+ new Config(
+ endpointFactory,
+ endpointFactory,
+ 4096,
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+ channelType,
+ protocol)
+ });
}
}
}
return params;
}
- private ClientEndpoint client;
- private ServerEndpoint server;
+ private SocketPair socketPair = new SocketPair();
private byte[] message;
private ExecutorService executor;
private Future<?> sendingFuture;
@@ -137,46 +157,78 @@ public final class ClientSocketPerfTest {
private static final AtomicLong bytesCounter = new AtomicLong();
private AtomicBoolean recording = new AtomicBoolean();
+ private static class SocketPair implements AutoCloseable {
+ public ClientEndpoint client;
+ public ServerEndpoint server;
+
+ SocketPair() {
+ client = null;
+ server = null;
+ }
+
+ @Override
+ public void close() {
+ if (client != null) {
+ client.stop();
+ }
+ if (server != null) {
+ server.stop();
+ }
+ }
+ }
+
private void setup(Config config) throws Exception {
message = newTextMessage(512);
// Always use the same server for consistency across the benchmarks.
- server = config.serverFactory().newServer(
- config.messageSize(), config.protocol().getProtocols(),
- ciphers(config));
-
- server.setMessageProcessor(new ServerEndpoint.MessageProcessor() {
- @Override
- public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
- if (recording.get()) {
- // Server received a message, increment the count.
- bytesCounter.addAndGet(numBytes);
- }
- }
- });
- Future<?> connectedFuture = server.start();
+ socketPair.server =
+ config.serverFactory()
+ .newServer(
+ config.messageSize(),
+ config.protocol().getProtocols(),
+ ciphers(config));
+ socketPair.server.init();
+
+ socketPair.server.setMessageProcessor(
+ new ServerEndpoint.MessageProcessor() {
+ @Override
+ public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
+ if (recording.get()) {
+ // Server received a message, increment the count.
+ bytesCounter.addAndGet(numBytes);
+ }
+ }
+ });
+ Future<?> connectedFuture = socketPair.server.start();
- client = config.clientFactory().newClient(
- config.channelType(), server.port(), config.protocol().getProtocols(), ciphers(config));
- client.start();
+ socketPair.client =
+ config.clientFactory()
+ .newClient(
+ config.channelType(),
+ socketPair.server.port(),
+ config.protocol().getProtocols(),
+ ciphers(config));
+ socketPair.client.start();
// Wait for the initial connection to complete.
connectedFuture.get(5, TimeUnit.SECONDS);
executor = Executors.newSingleThreadExecutor();
- sendingFuture = executor.submit(new Runnable() {
- @Override
- public void run() {
- try {
- Thread thread = Thread.currentThread();
- while (!stopping && !thread.isInterrupted()) {
- client.sendMessage(message);
- }
- } finally {
- client.flush();
- }
- }
- });
+ sendingFuture =
+ executor.submit(
+ new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Thread thread = Thread.currentThread();
+ while (!stopping && !thread.isInterrupted()) {
+ socketPair.client.sendMessage(message);
+ }
+ } finally {
+ socketPair.client.flush();
+ }
+ }
+ });
}
void close() throws Exception {
@@ -185,29 +237,37 @@ public final class ClientSocketPerfTest {
// Wait for the sending thread to stop.
sendingFuture.get(5, TimeUnit.SECONDS);
- client.stop();
- server.stop();
- executor.shutdown();
- executor.awaitTermination(5, TimeUnit.SECONDS);
+ if (socketPair != null) {
+ socketPair.close();
+ }
+ if (executor != null) {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
}
- /**
- * Simple benchmark for the amount of time to send a given number of messages
- */
+ /** Simple benchmark for the amount of time to send a given number of messages */
@Test
@Parameters(method = "getParams")
public void time(Config config) throws Exception {
- reset();
- setup(config);
- recording.set(true);
-
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- while (bytesCounter.get() < config.messageSize()) {
- }
- bytesCounter.set(0);
+ try {
+ reset();
+ setup(config);
+ recording.set(true);
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ while (bytesCounter.get() < config.messageSize()) {}
+ bytesCounter.set(0);
+ }
+ recording.set(false);
+ } finally {
+ close();
}
- recording.set(false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
close();
}
@@ -219,4 +279,4 @@ public final class ClientSocketPerfTest {
private String[] ciphers(Config config) {
return new String[] {config.cipher()};
}
-} \ No newline at end of file
+}
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
index 1e4f12460936..83eaaa1155e4 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerEndpoint.java
@@ -16,10 +16,14 @@
package android.conscrypt;
+import static org.conscrypt.TestUtils.getLoopbackAddress;
+
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.AutoCloseable;
+import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.SocketException;
import java.nio.channels.ClosedChannelException;
@@ -37,7 +41,7 @@ import javax.net.ssl.SSLSocketFactory;
/**
* A simple socket-based test server.
*/
-final class ServerEndpoint {
+final class ServerEndpoint implements AutoCloseable {
/**
* A processor for receipt of a single message.
*/
@@ -82,7 +86,11 @@ final class ServerEndpoint {
this.messageSize = messageSize;
this.protocols = protocols;
this.cipherSuites = cipherSuites;
- buffer = new byte[messageSize];
+ this.buffer = new byte[messageSize];
+ }
+
+ void init() throws IOException {
+ serverSocket.bind(new InetSocketAddress(getLoopbackAddress(), 0));
}
void setMessageProcessor(MessageProcessor messageProcessor) {
@@ -94,6 +102,11 @@ final class ServerEndpoint {
return executor.submit(new AcceptTask());
}
+ @Override
+ public void close() {
+ stop();
+ }
+
void stop() {
try {
stopping = true;
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index af3c405eab82..90a87ce0c69d 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -44,6 +44,7 @@ import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Rule;
+import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -115,14 +116,33 @@ public final class ServerSocketPerfTest {
return params;
}
- private ClientEndpoint client;
- private ServerEndpoint server;
+ private SocketPair socketPair = new SocketPair();
private ExecutorService executor;
private Future<?> receivingFuture;
private volatile boolean stopping;
private static final AtomicLong bytesCounter = new AtomicLong();
private AtomicBoolean recording = new AtomicBoolean();
+ private static class SocketPair implements AutoCloseable {
+ public ClientEndpoint client;
+ public ServerEndpoint server;
+
+ SocketPair() {
+ client = null;
+ server = null;
+ }
+
+ @Override
+ public void close() {
+ if (client != null) {
+ client.stop();
+ }
+ if (server != null) {
+ server.stop();
+ }
+ }
+ }
+
private void setup(final Config config) throws Exception {
recording.set(false);
@@ -130,9 +150,10 @@ public final class ServerSocketPerfTest {
final ChannelType channelType = config.channelType();
- server = config.serverFactory().newServer(config.messageSize(),
+ socketPair.server = config.serverFactory().newServer(config.messageSize(),
new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
- server.setMessageProcessor(new MessageProcessor() {
+ socketPair.server.init();
+ socketPair.server.setMessageProcessor(new MessageProcessor() {
@Override
public void processMessage(byte[] inMessage, int numBytes, OutputStream os) {
try {
@@ -151,20 +172,20 @@ public final class ServerSocketPerfTest {
}
});
- Future<?> connectedFuture = server.start();
+ Future<?> connectedFuture = socketPair.server.start();
// Always use the same client for consistency across the benchmarks.
- client = config.clientFactory().newClient(
- ChannelType.CHANNEL, server.port(),
+ socketPair.client = config.clientFactory().newClient(
+ ChannelType.CHANNEL, socketPair.server.port(),
new String[] {"TLSv1.3", "TLSv1.2"}, ciphers(config));
- client.start();
+ socketPair.client.start();
// Wait for the initial connection to complete.
connectedFuture.get(5, TimeUnit.SECONDS);
// Start the server-side streaming by sending a message to the server.
- client.sendMessage(message);
- client.flush();
+ socketPair.client.sendMessage(message);
+ socketPair.client.flush();
executor = Executors.newSingleThreadExecutor();
receivingFuture = executor.submit(new Runnable() {
@@ -173,7 +194,7 @@ public final class ServerSocketPerfTest {
Thread thread = Thread.currentThread();
byte[] buffer = new byte[config.messageSize()];
while (!stopping && !thread.isInterrupted()) {
- int numBytes = client.readMessage(buffer);
+ int numBytes = socketPair.client.readMessage(buffer);
if (numBytes < 0) {
return;
}
@@ -191,25 +212,38 @@ public final class ServerSocketPerfTest {
void close() throws Exception {
stopping = true;
// Stop and wait for sending to complete.
- server.stop();
- client.stop();
- executor.shutdown();
- receivingFuture.get(5, TimeUnit.SECONDS);
- executor.awaitTermination(5, TimeUnit.SECONDS);
+ if (socketPair != null) {
+ socketPair.close();
+ }
+ if (executor != null) {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+ if (receivingFuture != null) {
+ receivingFuture.get(5, TimeUnit.SECONDS);
+ }
}
@Test
@Parameters(method = "getParams")
public void throughput(Config config) throws Exception {
- setup(config);
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
- while (state.keepRunning()) {
- recording.set(true);
- while (bytesCounter.get() < config.messageSize()) {
- }
- bytesCounter.set(0);
- recording.set(false);
+ try {
+ setup(config);
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ recording.set(true);
+ while (bytesCounter.get() < config.messageSize()) {
+ }
+ bytesCounter.set(0);
+ recording.set(false);
+ }
+ } finally {
+ close();
}
+ }
+
+ @After
+ public void tearDown() throws Exception {
close();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8ffea237c71d..24594ab41100 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6204,7 +6204,7 @@ public class Notification implements Parcelable
int textColor = Colors.flattenAlpha(getPrimaryTextColor(p), pillColor);
contentView.setInt(R.id.expand_button, "setDefaultTextColor", textColor);
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
- // Use different highlighted colors for conversations' unread count
+ // Use different highlighted colors for e.g. unopened groups
if (p.mHighlightExpander) {
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
@@ -6453,7 +6453,10 @@ public class Notification implements Parcelable
big.setColorStateList(R.id.snooze_button, "setImageTintList", actionColor);
big.setColorStateList(R.id.bubble_button, "setImageTintList", actionColor);
- if (Flags.notificationsRedesignTemplates()) {
+ // Update margins to leave space for the top line (but not for HUNs, which use a
+ // different layout that already accounts for that).
+ if (Flags.notificationsRedesignTemplates()
+ && p.mViewType != StandardTemplateParams.VIEW_TYPE_HEADS_UP) {
int margin = getContentMarginTop(mContext,
R.dimen.notification_2025_content_margin_top);
big.setViewLayoutMargin(R.id.notification_main_column, RemoteViews.MARGIN_TOP,
@@ -6801,6 +6804,8 @@ public class Notification implements Parcelable
public RemoteViews makeNotificationGroupHeader() {
return makeNotificationHeader(mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_GROUP_HEADER)
+ // Highlight group expander until the group is first opened
+ .highlightExpander(Flags.notificationsRedesignTemplates())
.fillTextsFrom(this));
}
@@ -6817,6 +6822,12 @@ public class Notification implements Parcelable
getHeaderLayoutResource());
resetNotificationHeader(header);
bindNotificationHeader(header, p);
+ if (Flags.notificationsRedesignTemplates()
+ && (p.mViewType == StandardTemplateParams.VIEW_TYPE_MINIMIZED
+ || p.mViewType == StandardTemplateParams.VIEW_TYPE_PUBLIC)) {
+ // Center top line vertically in minimized and public header-only views
+ header.setBoolean(R.id.notification_header, "centerTopLine", true);
+ }
return header;
}
@@ -6970,12 +6981,14 @@ public class Notification implements Parcelable
* @param useRegularSubtext uses the normal subtext set if there is one available. Otherwise
* a new subtext is created consisting of the content of the
* notification.
+ * @param highlightExpander whether the expander should use the highlighted colors
* @hide
*/
- public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext) {
+ public RemoteViews makeLowPriorityContentView(boolean useRegularSubtext,
+ boolean highlightExpander) {
StandardTemplateParams p = mParams.reset()
.viewType(StandardTemplateParams.VIEW_TYPE_MINIMIZED)
- .highlightExpander(false)
+ .highlightExpander(highlightExpander)
.fillTextsFrom(this);
if (!useRegularSubtext || TextUtils.isEmpty(p.mSubText)) {
p.summaryText(createSummaryText());
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 42b798c8f939..3a0e6f187986 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -16,6 +16,10 @@
package android.view;
+import static android.app.Flags.notificationsRedesignTemplates;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -27,6 +31,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -174,6 +179,33 @@ public class NotificationHeaderView extends RelativeLayout {
}
/**
+ * Center top line and expand button vertically.
+ */
+ @RemotableViewMethod
+ public void centerTopLine(boolean center) {
+ if (notificationsRedesignTemplates()) {
+ // The content of the top line view is already center-aligned, but since the height
+ // matches the content by default, it looks top-aligned. If the height matches the
+ // parent instead, the text ends up correctly centered in the parent.
+ ViewGroup.LayoutParams lp = mTopLineView.getLayoutParams();
+ lp.height = center ? MATCH_PARENT : WRAP_CONTENT;
+ mTopLineView.setLayoutParams(lp);
+
+ centerExpandButton(center);
+ }
+ }
+
+ /** Center expand button vertically. */
+ private void centerExpandButton(boolean center) {
+ ViewGroup.LayoutParams lp = mExpandButton.getLayoutParams();
+ lp.height = center ? MATCH_PARENT : WRAP_CONTENT;
+ if (lp instanceof FrameLayout.LayoutParams flp) {
+ flp.gravity = center ? Gravity.CENTER : (Gravity.TOP | Gravity.END);
+ }
+ mExpandButton.setLayoutParams(lp);
+ }
+
+ /**
* This is used to make the low-priority header show the bolded text of a title.
*
* @param styleTextAsTitle true if this header's text is to have the style of a title
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 16cdb64f62cc..8ef0b0eebb8c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10527,28 +10527,7 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onInputEvent(InputEvent event) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
- List<InputEvent> processedEvents;
- try {
- processedEvents =
- mInputCompatProcessor.processInputEventForCompatibility(event);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- if (processedEvents != null) {
- if (processedEvents.isEmpty()) {
- // InputEvent consumed by mInputCompatProcessor
- finishInputEvent(event, true);
- } else {
- for (int i = 0; i < processedEvents.size(); i++) {
- enqueueInputEvent(
- processedEvents.get(i), this,
- QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
- }
- }
- } else {
- enqueueInputEvent(event, this, 0, true);
- }
+ processRawInputEvent(event);
}
@Override
@@ -10758,6 +10737,42 @@ public final class ViewRootImpl implements ViewParent,
final InvalidateOnAnimationRunnable mInvalidateOnAnimationRunnable =
new InvalidateOnAnimationRunnable();
+ /**
+ * Handle the incoming event.
+ *
+ * <p>The event will be first sent to the compatibility processor, which could choose to handle
+ * it. The compat processor could also choose to produce more synthetic events in response to
+ * the incoming one. Events that are not consumed by the compat processor are added to the
+ * {@link ViewRootImpl}'s queue for further processing inside ViewRootImpl.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public void processRawInputEvent(InputEvent event) {
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
+ List<InputEvent> processedEvents;
+ try {
+ processedEvents =
+ mInputCompatProcessor.processInputEventForCompatibility(event);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
+ if (processedEvents != null) {
+ if (processedEvents.isEmpty()) {
+ // InputEvent consumed by mInputCompatProcessor
+ mInputEventReceiver.finishInputEvent(event, true);
+ } else {
+ for (int i = 0; i < processedEvents.size(); i++) {
+ enqueueInputEvent(
+ processedEvents.get(i), mInputEventReceiver,
+ QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
+ }
+ }
+ } else {
+ enqueueInputEvent(event, mInputEventReceiver, 0, true);
+ }
+ }
+
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index b7537ed3f54b..80bc4fd89c8d 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -27,12 +27,12 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.view.RemotableViewMethod;
-import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -49,12 +49,15 @@ public class NotificationExpandButton extends FrameLayout {
private Drawable mPillDrawable;
private TextView mNumberView;
private ImageView mIconView;
+ private LinearLayout mPillView;
private boolean mExpanded;
private int mNumber;
private int mDefaultPillColor;
private int mDefaultTextColor;
private int mHighlightPillColor;
private int mHighlightTextColor;
+ // Track whether this ever had mExpanded = true, so that we don't highlight it anymore.
+ private boolean mWasExpanded = false;
public NotificationExpandButton(Context context) {
this(context, null, 0, 0);
@@ -78,8 +81,8 @@ public class NotificationExpandButton extends FrameLayout {
protected void onFinishInflate() {
super.onFinishInflate();
- final View pillView = findViewById(R.id.expand_button_pill);
- final LayerDrawable layeredPill = (LayerDrawable) pillView.getBackground();
+ mPillView = findViewById(R.id.expand_button_pill);
+ final LayerDrawable layeredPill = (LayerDrawable) mPillView.getBackground();
mPillDrawable = layeredPill.findDrawableByLayerId(R.id.expand_button_pill_colorized_layer);
mNumberView = findViewById(R.id.expand_button_number);
mIconView = findViewById(R.id.expand_button_icon);
@@ -133,6 +136,7 @@ public class NotificationExpandButton extends FrameLayout {
int contentDescriptionId;
if (mExpanded) {
if (notificationsRedesignTemplates()) {
+ mWasExpanded = true;
drawableId = R.drawable.ic_notification_2025_collapse;
} else {
drawableId = R.drawable.ic_collapse_notification;
@@ -152,6 +156,8 @@ public class NotificationExpandButton extends FrameLayout {
if (!notificationsRedesignTemplates()) {
// changing the expanded state can affect the number display
updateNumber();
+ } else {
+ updateColors();
}
}
@@ -166,27 +172,69 @@ public class NotificationExpandButton extends FrameLayout {
mNumberView.setVisibility(GONE);
}
- // changing number can affect the color
+ // changing number can affect the color and padding
updateColors();
+ updatePadding();
+ }
+
+ private void updatePadding() {
+ if (!notificationsRedesignTemplates()) {
+ return;
+ }
+
+ // Reduce the padding at the end when showing the number, since the arrow icon has more
+ // inherent spacing than the number does. This makes the content look more centered.
+ // Vertical padding remains unchanged.
+ int reducedPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_2025_expand_button_reduced_end_padding);
+ int normalPadding = getResources().getDimensionPixelSize(
+ R.dimen.notification_2025_expand_button_horizontal_icon_padding);
+ mPillView.setPaddingRelative(
+ /* start = */ normalPadding,
+ /* top = */ mPillView.getPaddingTop(),
+ /* end = */ shouldShowNumber() ? reducedPadding : normalPadding,
+ /* bottom = */ mPillView.getPaddingBottom()
+ );
+ }
+
+ /**
+ * Use highlight colors for the expander for groups (when the number is showing) that haven't
+ * been opened before, as long as the colors are available.
+ */
+ private boolean shouldBeHighlighted() {
+ return !mWasExpanded && shouldShowNumber()
+ && mHighlightPillColor != 0 && mHighlightTextColor != 0;
}
private void updateColors() {
- if (shouldShowNumber()) {
- if (mHighlightPillColor != 0) {
+ if (notificationsRedesignTemplates()) {
+ if (shouldBeHighlighted()) {
mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
- }
- mIconView.setColorFilter(mHighlightTextColor);
- if (mHighlightTextColor != 0) {
+ mIconView.setColorFilter(mHighlightTextColor);
mNumberView.setTextColor(mHighlightTextColor);
- }
- } else {
- if (mDefaultPillColor != 0) {
+ } else {
mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
- }
- mIconView.setColorFilter(mDefaultTextColor);
- if (mDefaultTextColor != 0) {
+ mIconView.setColorFilter(mDefaultTextColor);
mNumberView.setTextColor(mDefaultTextColor);
}
+ } else {
+ if (shouldShowNumber()) {
+ if (mHighlightPillColor != 0) {
+ mPillDrawable.setTintList(ColorStateList.valueOf(mHighlightPillColor));
+ }
+ mIconView.setColorFilter(mHighlightTextColor);
+ if (mHighlightTextColor != 0) {
+ mNumberView.setTextColor(mHighlightTextColor);
+ }
+ } else {
+ if (mDefaultPillColor != 0) {
+ mPillDrawable.setTintList(ColorStateList.valueOf(mDefaultPillColor));
+ }
+ mIconView.setColorFilter(mDefaultTextColor);
+ if (mDefaultTextColor != 0) {
+ mNumberView.setTextColor(mDefaultTextColor);
+ }
+ }
}
}
diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid.xml b/core/res/res/drawable/ic_accessibility_hearing_aid.xml
index e5ffeb0274fc..79c61a64302d 100644
--- a/core/res/res/drawable/ic_accessibility_hearing_aid.xml
+++ b/core/res/res/drawable/ic_accessibility_hearing_aid.xml
@@ -17,8 +17,10 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/accessibility_feature_background" />
<foreground>
+ <!-- TODO: b/382377298 - To update correct drawable with same inset
+ @dimen/accessibility_icon_foreground_padding_ratio or remove the inset together. -->
<inset
android:drawable="@drawable/ic_accessibility_hearing_aid_foreground"
- android:inset="@dimen/accessibility_icon_foreground_padding_ratio" />
+ android:inset="30%" />
</foreground>
</adaptive-icon>
diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml
new file mode 100644
index 000000000000..4f0036ccd8d2
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_hearing_aid_blue_dot.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14"
+ android:viewportHeight="14">
+ <path
+ android:pathData="M7,7m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"
+ android:strokeWidth="2"
+ android:fillColor="#3998D3"
+ android:strokeColor="#3E373C"/>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml
new file mode 100644
index 000000000000..2b302e18ad37
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@color/accessibility_feature_background" />
+ <foreground>
+ <!-- TODO: b/382377298 - To update correct drawable with same inset
+ @dimen/accessibility_icon_foreground_padding_ratio or remove the inset together. -->
+ <inset
+ android:drawable="@drawable/ic_accessibility_hearing_aid_disconnected_foreground"
+ android:inset="30%" />
+ </foreground>
+</adaptive-icon>
diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml
new file mode 100644
index 000000000000..1097ed9067d7
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_hearing_aid_disconnected_foreground.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="22dp"
+ android:height="23dp"
+ android:viewportWidth="22"
+ android:viewportHeight="23">
+ <path
+ android:pathData="M13,1C14.275,1 15.42,1.284 16.435,1.851L14.934,3.352C14.346,3.117 13.701,3 13,3C11.583,3 10.396,3.479 9.438,4.438C8.479,5.396 8,6.583 8,8C8,8.671 8.109,9.324 8.326,9.96L6.794,11.492C6.265,10.355 6,9.191 6,8C6,6.033 6.675,4.375 8.025,3.025C9.375,1.675 11.033,1 13,1ZM13,4.5C13.251,4.5 13.491,4.522 13.72,4.566L9.566,8.72C9.522,8.49 9.5,8.25 9.5,8C9.5,7.033 9.842,6.208 10.525,5.525C11.208,4.842 12.033,4.5 13,4.5ZM9.332,14.582L1.707,22.207L0.293,20.793L20.293,0.793L21.707,2.207L19.22,4.694C19.74,5.677 20,6.779 20,8H18C18,7.356 17.901,6.76 17.703,6.211L16.465,7.449C16.514,7.793 16.509,8.152 16.45,8.525L20.575,12.1L19.075,13.3C19.408,13.833 19.646,14.421 19.788,15.063C19.929,15.704 20,16.35 20,17C20,18.1 19.608,19.042 18.825,19.825C18.042,20.608 17.1,21 16,21C15.05,21 14.217,20.717 13.5,20.15C12.783,19.583 12.267,18.833 11.95,17.9C11.667,17.067 11.363,16.45 11.038,16.05C10.712,15.65 10.175,15.183 9.425,14.65C9.394,14.628 9.363,14.605 9.332,14.582ZM10.756,13.158C11.518,13.704 12.129,14.23 12.587,14.738C13.063,15.262 13.442,15.975 13.725,16.875C13.908,17.458 14.188,17.958 14.563,18.375C14.938,18.792 15.417,19 16,19C16.533,19 17,18.804 17.4,18.413C17.8,18.021 18,17.55 18,17C18,16.567 17.958,16.138 17.875,15.712C17.792,15.288 17.658,14.9 17.475,14.55L15.775,15.9L13.275,11.5C12.985,11.524 12.706,11.516 12.438,11.477L10.756,13.158ZM3.989,14.297L5.434,12.852C5.145,12.405 4.896,11.934 4.688,11.438C4.229,10.346 4,9.2 4,8C4,6.783 4.229,5.625 4.688,4.525C5.146,3.425 5.8,2.45 6.65,1.6L5.2,0.2C4.167,1.25 3.375,2.446 2.825,3.788C2.275,5.129 2,6.533 2,8C2,9.433 2.275,10.821 2.825,12.163C3.136,12.921 3.524,13.632 3.989,14.297ZM16.375,12.9L17.425,12.05L15.55,10.4L15.363,10.587C15.304,10.646 15.242,10.7 15.175,10.75L16.375,12.9Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml b/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml
new file mode 100644
index 000000000000..e8f0063bb4a8
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_hearing_aid_green_dot.xml
@@ -0,0 +1,27 @@
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="14dp"
+ android:height="14dp"
+ android:viewportWidth="14"
+ android:viewportHeight="14">
+ <path
+ android:pathData="M7,7m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"
+ android:strokeWidth="2"
+ android:fillColor="#6DD58C"
+ android:strokeColor="#3E373C"/>
+</vector>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index a4735fe6c7af..2adb79118ed9 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -408,6 +408,9 @@
<!-- the padding of the expand icon in the notification header -->
<dimen name="notification_2025_expand_button_horizontal_icon_padding">6dp</dimen>
+ <!-- a smaller padding for the end of the expand button, for use when showing the number -->
+ <dimen name="notification_2025_expand_button_reduced_end_padding">4dp</dimen>
+
<!-- the size of the notification close button -->
<dimen name="notification_close_button_size">16dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 33d3858562af..6313054e47f5 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4947,6 +4947,18 @@
<!-- Title of hearing aids feature, shown in the warning dialog about the accessibility shortcut. [CHAR LIMIT=none] -->
<string name="hearing_aids_feature_name">Hearing devices</string>
+ <!-- Text of hearing device disconnected state. [CHAR LIMIT=20] -->
+ <string name="hearing_device_status_disconnected">Disconnected</string>
+
+ <!-- Text of hearing device connected state. [CHAR LIMIT=20] -->
+ <string name="hearing_device_status_connected">Connected</string>
+
+ <!-- Text of hearing device active state. Active means device is currently connected and able to streaming the sound. [CHAR LIMIT=20] -->
+ <string name="hearing_device_status_active">Active</string>
+
+ <!-- Text of hearing device loading state. Loading means device is not available yet, it might be in connecting or disconnecting. [CHAR LIMIT=20] -->
+ <string name="hearing_device_status_loading">Loading</string>
+
<!-- Text in toast to alert the user that the accessibility shortcut turned on an accessibility service. [CHAR LIMIT=none] -->
<string name="accessibility_shortcut_enabling_service">Held volume keys. <xliff:g id="service_name" example="TalkBack">%1$s</xliff:g> turned on.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4789624685c2..a18f923d625b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3247,6 +3247,8 @@
<java-symbol type="dimen" name="notification_content_margin" />
<java-symbol type="dimen" name="notification_2025_margin" />
<java-symbol type="dimen" name="notification_2025_content_margin_top" />
+ <java-symbol type="dimen" name="notification_2025_expand_button_horizontal_icon_padding" />
+ <java-symbol type="dimen" name="notification_2025_expand_button_reduced_end_padding" />
<java-symbol type="dimen" name="notification_progress_margin_horizontal" />
<java-symbol type="dimen" name="notification_header_background_height" />
<java-symbol type="dimen" name="notification_header_touchable_height" />
@@ -3821,10 +3823,18 @@
<java-symbol type="drawable" name="ic_accessibility_color_correction" />
<java-symbol type="drawable" name="ic_accessibility_generic" />
<java-symbol type="drawable" name="ic_accessibility_hearing_aid" />
+ <java-symbol type="drawable" name="ic_accessibility_hearing_aid_disconnected" />
+ <java-symbol type="drawable" name="ic_accessibility_hearing_aid_green_dot" />
+ <java-symbol type="drawable" name="ic_accessibility_hearing_aid_blue_dot" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
<java-symbol type="drawable" name="ic_accessibility_reduce_bright_colors" />
<java-symbol type="drawable" name="ic_accessibility_one_handed" />
+ <java-symbol type="string" name="hearing_device_status_disconnected" />
+ <java-symbol type="string" name="hearing_device_status_connected" />
+ <java-symbol type="string" name="hearing_device_status_active" />
+ <java-symbol type="string" name="hearing_device_status_loading" />
+
<java-symbol type="string" name="hearing_aids_feature_name" />
<java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="one_handed_mode_feature_name" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 47032fd8616f..13f8e9ef9dd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -222,15 +222,9 @@ public class BubbleExpandedView extends LinearLayout {
mTaskView.getBoundsOnScreen(launchBounds);
options.setTaskAlwaysOnTop(true);
- options.setLaunchedFromBubble(true);
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
|| (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
@@ -242,7 +236,6 @@ public class BubbleExpandedView extends LinearLayout {
context,
/* requestCode= */ 0,
mBubble.getAppBubbleIntent()
- .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
/* options= */ null);
@@ -250,13 +243,19 @@ public class BubbleExpandedView extends LinearLayout {
launchBounds);
} else if (!mIsOverflow && isShortcutBubble) {
ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey());
+ options.setLaunchedFromBubble(true);
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
options, launchBounds);
} else {
+ options.setLaunchedFromBubble(true);
if (mBubble != null) {
mBubble.setIntentActive();
}
+ final Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
mTaskView.startActivity(mPendingIntent, fillInIntent, options,
launchBounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index 0c0fd7b10f6e..89c038b4a26b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -105,15 +105,8 @@ public class BubbleTaskViewHelper {
getBubbleKey());
try {
options.setTaskAlwaysOnTop(true);
- options.setLaunchedFromBubble(true);
options.setPendingIntentBackgroundActivityStartMode(
MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
-
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-
final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
|| (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));
if (mBubble.isAppBubble()) {
@@ -124,20 +117,25 @@ public class BubbleTaskViewHelper {
context,
/* requestCode= */ 0,
mBubble.getAppBubbleIntent()
- .addFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK),
PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT,
/* options= */ null);
mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
launchBounds);
} else if (isShortcutBubble) {
+ options.setLaunchedFromBubble(true);
options.setApplyActivityFlagsForBubbles(true);
mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
options, launchBounds);
} else {
+ options.setLaunchedFromBubble(true);
if (mBubble != null) {
mBubble.setIntentActive();
}
+ final Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
mTaskView.startActivity(mPendingIntent, fillInIntent, options,
launchBounds);
}
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 7e655235732f..68c1983825a2 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -1269,9 +1269,6 @@ int APerformanceHint_notifyWorkloadIncrease(APerformanceHintSession* session, bo
const char* debugName) {
VALIDATE_PTR(session)
VALIDATE_PTR(debugName)
- if (!useNewLoadHintBehavior()) {
- return ENOTSUP;
- }
return session->notifyWorkloadIncrease(cpu, gpu, debugName);
}
@@ -1279,9 +1276,6 @@ int APerformanceHint_notifyWorkloadReset(APerformanceHintSession* session, bool
const char* debugName) {
VALIDATE_PTR(session)
VALIDATE_PTR(debugName)
- if (!useNewLoadHintBehavior()) {
- return ENOTSUP;
- }
return session->notifyWorkloadReset(cpu, gpu, debugName);
}
@@ -1289,9 +1283,6 @@ int APerformanceHint_notifyWorkloadSpike(APerformanceHintSession* session, bool
const char* debugName) {
VALIDATE_PTR(session)
VALIDATE_PTR(debugName)
- if (!useNewLoadHintBehavior()) {
- return ENOTSUP;
- }
return session->notifyWorkloadSpike(cpu, gpu, debugName);
}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
index ef46906f9cf4..e4f07f9fc213 100644
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
@@ -1364,8 +1364,6 @@ public class PackageWatchdog {
Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
mPolicyFile.failWrite(stream);
return false;
- } finally {
- IoUtils.closeQuietly(stream);
}
}
}
diff --git a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
index 4a00ed3d1d46..5297c8b61aad 100644
--- a/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
+++ b/packages/CrashRecovery/services/platform/java/com/android/server/PackageWatchdog.java
@@ -1372,8 +1372,6 @@ public class PackageWatchdog {
Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
mPolicyFile.failWrite(stream);
return false;
- } finally {
- IoUtils.closeQuietly(stream);
}
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index 61b8b7f73754..41b1319a1fc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -54,9 +54,11 @@ inline fun <T> Flow<List<T>>.filterItem(crossinline predicate: (T) -> Boolean):
fun <T1, T2> Flow<T1>.waitFirst(otherFlow: Flow<T2>): Flow<T1> =
combine(otherFlow.take(1)) { value, _ -> value }
-
/**
* Collects the latest value of given flow with a provided action with [LifecycleOwner].
+ *
+ * This helper function is designed to work with non Compose code. For Compose, please collect the
+ * flow in a `LaunchedEffect` instead to ensure disposable and re-enter safe.
*/
fun <T> Flow<T>.collectLatestWithLifecycle(
lifecycleOwner: LifecycleOwner,
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
index 0d73cb3e63c9..798e2d49ff57 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPageTest.kt
@@ -160,23 +160,25 @@ class TogglePermissionAppInfoPageTest {
@Test
fun infoPage_whenChangeableAndClick() {
val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = true)
+ val switchTitle = context.getString(listModel.switchTitleResId)
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
+ composeTestRule.waitUntilExists(hasText(switchTitle))
+ composeTestRule.onNodeWithText(switchTitle).performClick()
- composeTestRule.waitUntilExists(
- hasText(context.getString(listModel.switchTitleResId)) and isOn())
+ composeTestRule.waitUntilExists(hasText(switchTitle) and isOn())
}
@Test
fun infoPage_whenNotChangeableAndClick() {
val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)
+ val switchTitle = context.getString(listModel.switchTitleResId)
setTogglePermissionAppInfoPage(listModel)
- composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId)).performClick()
+ composeTestRule.waitUntilExists(hasText(switchTitle))
+ composeTestRule.onNodeWithText(switchTitle).performClick()
- composeTestRule.waitUntilExists(
- hasText(context.getString(listModel.switchTitleResId)) and isOff())
+ composeTestRule.waitUntilExists(hasText(switchTitle) and isOff())
}
@Test
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 31d5bfaccd5d..99d704f1eca8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -86,6 +86,7 @@
<uses-permission android:name="android.permission.LOCATION_HARDWARE" />
<uses-permission android:name="android.permission.NETWORK_FACTORY" />
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
+ <uses-permission android:name="android.permission.INTERNET" />
<!-- Physical hardware -->
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" />
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
index b08f97a646b9..e00f6a6d6418 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
@@ -16,11 +16,16 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.when;
+import android.content.ComponentName;
import android.graphics.drawable.Drawable;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
@@ -28,15 +33,18 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
import com.android.systemui.res.R;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
import java.util.List;
@@ -44,32 +52,35 @@ import java.util.List;
/** Tests for {@link AccessibilityTargetAdapter}. */
@SmallTest
@RunWith(AndroidJUnit4.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class AccessibilityTargetAdapterTest extends SysuiTestCase {
+ @Rule
+ public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ private static final ComponentName TEST_NAME = new ComponentName("test.pkg", "test.activitty");
+ private static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1;
+
@Mock
private AccessibilityTarget mAccessibilityTarget;
-
@Mock
private Drawable mIcon;
-
@Mock
private Drawable.ConstantState mConstantState;
-
private ViewHolder mViewHolder;
private AccessibilityTargetAdapter mAdapter;
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@Before
public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mTargets.add(mAccessibilityTarget);
- mAdapter = new AccessibilityTargetAdapter(mTargets);
-
final View rootView = LayoutInflater.from(mContext).inflate(
R.layout.accessibility_floating_menu_item, null);
mViewHolder = new ViewHolder(rootView);
when(mAccessibilityTarget.getIcon()).thenReturn(mIcon);
+ when(mAccessibilityTarget.getId()).thenReturn(TEST_NAME.flattenToString());
when(mIcon.getConstantState()).thenReturn(mConstantState);
+
+ mTargets.add(mAccessibilityTarget);
+ mAdapter = new AccessibilityTargetAdapter(mTargets);
}
@Test
@@ -105,4 +116,77 @@ public class AccessibilityTargetAdapterTest extends SysuiTestCase {
assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals(
"testState")).isTrue();
}
+
+ @Test
+ @EnableFlags(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
+ public void onHearingDeviceStatusChanged_disconnected_getExpectedStateDescription() {
+ when(mAccessibilityTarget.getId()).thenReturn(
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+ int indexInTarget = 0;
+
+ mAdapter.onHearingDeviceStatusChanged(ConnectionStatus.DISCONNECTED, indexInTarget);
+ mAdapter.onBindViewHolder(mViewHolder, indexInTarget);
+
+ assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals(
+ "Disconnected")).isTrue();
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
+ public void onBindViewHolder_withPayloadDisconnected_getExpectedStateDescription() {
+ when(mAccessibilityTarget.getId()).thenReturn(
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+ int indexInTarget = 0;
+
+ mAdapter.onHearingDeviceStatusChanged(ConnectionStatus.DISCONNECTED, indexInTarget);
+ mAdapter.onBindViewHolder(mViewHolder, indexInTarget,
+ List.of(PAYLOAD_HEARING_STATUS_DRAWABLE));
+
+ assertThat(mViewHolder.itemView.getStateDescription().toString().contentEquals(
+ "Disconnected")).isTrue();
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
+ public void setBadgeOnLeftSide_false_rightBadgeVisibleAndLeftBadgeInvisible() {
+ when(mAccessibilityTarget.getId()).thenReturn(
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+
+ mAdapter.setBadgeOnLeftSide(false);
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
+ public void setBadgeOnLeftSide_rightBadgeInvisibleAndLeftBadgeVisible() {
+ when(mAccessibilityTarget.getId()).thenReturn(
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+
+ mAdapter.setBadgeOnLeftSide(true);
+ mAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ @EnableFlags(
+ com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICE_SET_CONNECTION_STATUS_REPORT)
+ public void setBadgeOnLeftSide_bindViewHolderPayloads_rightBadgeInvisibleAndLeftBadgeVisible() {
+ when(mAccessibilityTarget.getId()).thenReturn(
+ ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString());
+
+ mAdapter.setBadgeOnLeftSide(true);
+ mAdapter.onBindViewHolder(mViewHolder, 0, List.of(PAYLOAD_HEARING_STATUS_DRAWABLE));
+
+ assertThat(mViewHolder.mRightBadgeView.getVisibility()).isEqualTo(View.INVISIBLE);
+ assertThat(mViewHolder.mLeftBadgeView.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index fbd8a713a249..715c40a31632 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -113,7 +113,8 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDragToInteractAnimationController);
- final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
+ final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(
+ mStubTargets);
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
mStubListView.setAdapter(stubAdapter);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 14bbd38ece2c..72a91bc12f8d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -279,7 +279,7 @@ public class NotificationChildrenContainerTest extends SysuiTestCase {
notification);
RemoteViews headerRemoteViews;
if (lowPriority) {
- headerRemoteViews = builder.makeLowPriorityContentView(true);
+ headerRemoteViews = builder.makeLowPriorityContentView(true, false);
} else {
headerRemoteViews = builder.makeNotificationGroupHeader();
}
diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
index 2067f85249a2..b1e4272c4004 100644
--- a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
+++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml
@@ -15,18 +15,60 @@
limitations under the License.
-->
-<LinearLayout
+<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="@dimen/accessibility_floating_menu_small_padding"
android:paddingEnd="@dimen/accessibility_floating_menu_small_padding"
- android:orientation="vertical"
- android:gravity="center">
+ android:orientation="vertical">
<View
android:id="@+id/icon_view"
android:layout_width="@dimen/accessibility_floating_menu_small_width_height"
- android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/>
+ android:layout_height="@dimen/accessibility_floating_menu_small_width_height"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"/>
-</LinearLayout> \ No newline at end of file
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/app_icon_constraint_right_badge_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_badge_position" />
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/app_icon_constraint_badge_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_badge_position" />
+ <androidx.constraintlayout.widget.Guideline
+ android:id="@+id/app_icon_constraint_left_badge_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_percent="@dimen/accessibility_floating_menu_left_badge_x_position" />
+
+ <View
+ android:id="@+id/right_badge_view"
+ android:layout_width="@dimen/accessibility_floating_menu_small_badge_width_height"
+ android:layout_height="@dimen/accessibility_floating_menu_small_badge_width_height"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_right_badge_vertical"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_icon_constraint_badge_horizontal"
+ android:visibility="invisible" />
+
+ <View
+ android:id="@+id/left_badge_view"
+ android:layout_width="@dimen/accessibility_floating_menu_small_badge_width_height"
+ android:layout_height="@dimen/accessibility_floating_menu_small_badge_width_height"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintLeft_toLeftOf="@id/app_icon_constraint_left_badge_vertical"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_icon_constraint_badge_horizontal"
+ android:visibility="invisible" />
+
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 11327b648ddc..570197edf876 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1683,10 +1683,14 @@
<dimen name="accessibility_floating_menu_margin">16dp</dimen>
<dimen name="accessibility_floating_menu_small_padding">6dp</dimen>
<dimen name="accessibility_floating_menu_small_width_height">36dp</dimen>
+ <dimen name="accessibility_floating_menu_small_badge_width_height">12dp</dimen>
+ <dimen name="accessibility_floating_menu_badge_position">0.67</dimen>
+ <dimen name="accessibility_floating_menu_left_badge_x_position">-0.67</dimen>
<dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen>
<dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen>
<dimen name="accessibility_floating_menu_large_padding">8dp</dimen>
<dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
+ <dimen name="accessibility_floating_menu_large_badge_width_height">18dp</dimen>
<dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
<dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
index 5160309e7c72..697d16b3336c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java
@@ -16,12 +16,17 @@
package com.android.systemui.accessibility.floatingmenu;
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
+
+import android.content.ComponentName;
+import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -29,7 +34,10 @@ import androidx.recyclerview.widget.RecyclerView.Adapter;
import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
+import com.android.settingslib.bluetooth.HearingAidDeviceManager;
+import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus;
import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder;
+import com.android.systemui.accessibility.hearingaid.HearingDeviceStatusDrawableInfo;
import com.android.systemui.res.R;
import java.lang.annotation.Retention;
@@ -40,10 +48,17 @@ import java.util.List;
* An adapter which shows the set of accessibility targets that can be performed.
*/
public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
+ @VisibleForTesting
+ static final int PAYLOAD_HEARING_STATUS_DRAWABLE = 1;
+
private int mIconWidthHeight;
+ private int mBadgeWidthHeight;
private int mItemPadding;
private final List<AccessibilityTarget> mTargets;
+ private int mHearingDeviceStatus;
+ private boolean mBadgeOnLeftSide = false;
+
@IntDef({
ItemType.FIRST_ITEM,
ItemType.REGULAR_ITEM,
@@ -56,7 +71,7 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
int LAST_ITEM = 2;
}
- public AccessibilityTargetAdapter(List<AccessibilityTarget> targets) {
+ public AccessibilityTargetAdapter(@NonNull List<AccessibilityTarget> targets) {
mTargets = targets;
}
@@ -82,7 +97,9 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final AccessibilityTarget target = mTargets.get(position);
holder.mIconView.setBackground(target.getIcon());
- holder.updateIconWidthHeight(mIconWidthHeight);
+ holder.mRightBadgeView.setBackground(null);
+ holder.mLeftBadgeView.setBackground(null);
+ holder.updateIconSize(mIconWidthHeight);
holder.updateItemPadding(mItemPadding, getItemCount());
holder.itemView.setOnClickListener((v) -> target.onSelected());
holder.itemView.setStateDescription(target.getStateDescription());
@@ -95,6 +112,32 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
ViewCompat.replaceAccessibilityAction(holder.itemView,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
clickHint, /* command= */ null);
+
+ if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) {
+ if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(
+ ComponentName.unflattenFromString(target.getId()))) {
+ updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus);
+ }
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position,
+ @NonNull List<Object> payloads) {
+ if (payloads.isEmpty()) {
+ onBindViewHolder(holder, position);
+ return;
+ }
+
+ if (com.android.settingslib.flags.Flags.hearingDeviceSetConnectionStatusReport()) {
+ payloads.forEach(payload -> {
+ if (payload instanceof Integer cmd) {
+ if (cmd == PAYLOAD_HEARING_STATUS_DRAWABLE) {
+ updateHearingDeviceStatusDrawable(holder, mHearingDeviceStatus);
+ }
+ }
+ });
+ }
}
@ItemType
@@ -122,19 +165,76 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
mIconWidthHeight = iconWidthHeight;
}
+ public void setBadgeWidthHeight(int badgeWidthHeight) {
+ mBadgeWidthHeight = badgeWidthHeight;
+ }
+
public void setItemPadding(int itemPadding) {
mItemPadding = itemPadding;
}
+ public void setBadgeOnLeftSide(boolean leftSide) {
+ mBadgeOnLeftSide = leftSide;
+ }
+
+ /**
+ * Notifies to update the hearing device status drawable at the given target index.
+ *
+ * @param status the connection status for hearing devices.
+ * {@link HearingAidDeviceManager.ConnectionStatus}
+ * @param targetIndex The index of the hearing aid device in the target list, or -1 if not
+ * exist.
+ */
+ public void onHearingDeviceStatusChanged(@HearingAidDeviceManager.ConnectionStatus int status,
+ int targetIndex) {
+ mHearingDeviceStatus = status;
+
+ if (targetIndex >= 0) {
+ notifyItemChanged(targetIndex, PAYLOAD_HEARING_STATUS_DRAWABLE);
+ }
+ }
+
+ private void updateHearingDeviceStatusDrawable(ViewHolder holder,
+ @ConnectionStatus int status) {
+ final Context context = holder.itemView.getContext();
+ HearingDeviceStatusDrawableInfo.StatusDrawableInfo statusDrawableInfo =
+ HearingDeviceStatusDrawableInfo.get(status);
+ final int baseDrawableId = statusDrawableInfo.baseDrawableId();
+ final int stateDescriptionId = statusDrawableInfo.stateDescriptionId();
+ final int indicatorDrawableId = statusDrawableInfo.indicatorDrawableId();
+
+ holder.mIconView.setBackground(
+ (baseDrawableId != 0) ? context.getDrawable(baseDrawableId) : null);
+ holder.mRightBadgeView.setBackground(
+ (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null);
+ holder.mLeftBadgeView.setBackground(
+ (indicatorDrawableId != 0) ? context.getDrawable(indicatorDrawableId) : null);
+ holder.itemView.setStateDescription(
+ (stateDescriptionId != 0) ? context.getString(stateDescriptionId) : null);
+ holder.updateBadgeSize(mBadgeWidthHeight);
+
+ if (mBadgeOnLeftSide) {
+ holder.mRightBadgeView.setVisibility(View.INVISIBLE);
+ holder.mLeftBadgeView.setVisibility(View.VISIBLE);
+ } else {
+ holder.mRightBadgeView.setVisibility(View.VISIBLE);
+ holder.mLeftBadgeView.setVisibility(View.INVISIBLE);
+ }
+ }
+
static class ViewHolder extends RecyclerView.ViewHolder {
final View mIconView;
+ final View mRightBadgeView;
+ final View mLeftBadgeView;
ViewHolder(View itemView) {
super(itemView);
mIconView = itemView.findViewById(R.id.icon_view);
+ mRightBadgeView = itemView.findViewById(R.id.right_badge_view);
+ mLeftBadgeView = itemView.findViewById(R.id.left_badge_view);
}
- void updateIconWidthHeight(int newValue) {
+ void updateIconSize(int newValue) {
final ViewGroup.LayoutParams layoutParams = mIconView.getLayoutParams();
if (layoutParams.width == newValue) {
return;
@@ -144,6 +244,24 @@ public class AccessibilityTargetAdapter extends Adapter<ViewHolder> {
mIconView.setLayoutParams(layoutParams);
}
+ void updateBadgeSize(int newValue) {
+ final ViewGroup.LayoutParams rightLayoutParams = mRightBadgeView.getLayoutParams();
+ if (rightLayoutParams.width == newValue) {
+ return;
+ }
+ rightLayoutParams.width = newValue;
+ rightLayoutParams.height = newValue;
+ final ViewGroup.LayoutParams leftLayoutParams = mLeftBadgeView.getLayoutParams();
+ if (leftLayoutParams.width == newValue) {
+ return;
+ }
+ leftLayoutParams.width = newValue;
+ leftLayoutParams.height = newValue;
+
+ mRightBadgeView.setLayoutParams(rightLayoutParams);
+ mLeftBadgeView.setLayoutParams(leftLayoutParams);
+ }
+
void updateItemPadding(int padding, int size) {
itemView.setPaddingRelative(padding, padding, padding, 0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
index 23fc546dd9b3..3f49010aaaab 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java
@@ -90,6 +90,7 @@ class MenuView extends FrameLayout implements
mTargetFeaturesView = new RecyclerView(context);
mTargetFeaturesView.setAdapter(mAdapter);
mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context));
+ mTargetFeaturesView.setClipChildren(false);
setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
// Avoid drawing out of bounds of the parent view
setClipToOutline(true);
@@ -157,6 +158,14 @@ class MenuView extends FrameLayout implements
private void onItemSizeChanged() {
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize());
+ mAdapter.setBadgeWidthHeight(mMenuViewAppearance.getBadgeIconSize());
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @SuppressLint("NotifyDataSetChanged")
+ void onSideChanged() {
+ // Badge should be on different side of Menu view's side.
+ mAdapter.setBadgeOnLeftSide(!mMenuViewAppearance.isMenuOnLeftSide());
mAdapter.notifyDataSetChanged();
}
@@ -205,6 +214,7 @@ class MenuView extends FrameLayout implements
mMenuViewAppearance.setPercentagePosition(percentagePosition);
onPositionChanged();
+ onSideChanged();
}
void onPositionChanged() {
@@ -246,6 +256,8 @@ class MenuView extends FrameLayout implements
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize());
+ mAdapter.setBadgeWidthHeight(mMenuViewAppearance.getBadgeIconSize());
+
mAdapter.notifyDataSetChanged();
onSizeChanged();
@@ -313,6 +325,7 @@ class MenuView extends FrameLayout implements
mMenuViewAppearance.setPercentagePosition(percentagePosition);
onEdgeChangedIfNeeded();
+ onSideChanged();
}
boolean isMoveToTucked() {
@@ -434,11 +447,21 @@ class MenuView extends FrameLayout implements
}
private void updateHearingDeviceStatus(@HearingAidDeviceManager.ConnectionStatus int status) {
- // TODO: b/357882387 - To update status drawable according to the status here.
+ final int haStatus = mMenuViewModel.getHearingDeviceStatusData().getValue();
+ final int haPosition = mMenuViewModel.getHearingDeviceTargetIndexData().getValue();
+ if (haPosition >= 0) {
+ mContext.getMainExecutor().execute(
+ () -> mAdapter.onHearingDeviceStatusChanged(haStatus, haPosition));
+ }
}
private void updateHearingDeviceTargetIndex(int position) {
- // TODO: b/357882387 - To update status drawable according to the status here.
+ final int haStatus = mMenuViewModel.getHearingDeviceStatusData().getValue();
+ final int haPosition = mMenuViewModel.getHearingDeviceTargetIndexData().getValue();
+ if (haPosition >= 0) {
+ mContext.getMainExecutor().execute(
+ () -> mAdapter.onHearingDeviceStatusChanged(haStatus, haPosition));
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 760e1c374e31..a700cbef2e16 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -57,6 +57,8 @@ class MenuViewAppearance {
private int mLargePadding;
private int mSmallIconSize;
private int mLargeIconSize;
+ private int mSmallBadgeSize;
+ private int mLargeBadgeSize;
private int mSmallSingleRadius;
private int mSmallMultipleRadius;
private int mLargeSingleRadius;
@@ -97,6 +99,12 @@ class MenuViewAppearance {
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height);
mLargeIconSize =
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_width_height);
+ mSmallBadgeSize =
+ mRes.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_small_badge_width_height);
+ mLargeBadgeSize =
+ mRes.getDimensionPixelSize(
+ R.dimen.accessibility_floating_menu_large_badge_width_height);
mSmallSingleRadius =
mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius);
mSmallMultipleRadius = mRes.getDimensionPixelSize(
@@ -211,6 +219,10 @@ class MenuViewAppearance {
return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize;
}
+ int getBadgeIconSize() {
+ return mSizeType == SMALL ? mSmallBadgeSize : mLargeBadgeSize;
+ }
+
private int getMenuMargin() {
return mMargin;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java
new file mode 100644
index 000000000000..ff9bfec7f5bd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDeviceStatusDrawableInfo.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import com.android.settingslib.bluetooth.HearingAidDeviceManager.ConnectionStatus;
+
+/**
+ * A utility class to get the hearing device status drawable and its description for the
+ * given connection status. Hearing device status drawable combine with base and indicator
+ * drawable.
+ */
+public final class HearingDeviceStatusDrawableInfo {
+
+ private static final StatusDrawableInfo DRAWABLE_DEFAULT_INFO = new StatusDrawableInfo(
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid,
+ 0,
+ 0);
+ private static final StatusDrawableInfo DRAWABLE_DISCONNECTED_INFO = new StatusDrawableInfo(
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid_disconnected,
+ 0,
+ com.android.internal.R.string.hearing_device_status_disconnected);
+ private static final StatusDrawableInfo DRAWABLE_CONNECTED_INFO = new StatusDrawableInfo(
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid,
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid_blue_dot,
+ com.android.internal.R.string.hearing_device_status_connected);
+ private static final StatusDrawableInfo DRAWABLE_ACTIVE_INFO = new StatusDrawableInfo(
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid,
+ com.android.internal.R.drawable.ic_accessibility_hearing_aid_green_dot,
+ com.android.internal.R.string.hearing_device_status_active);
+
+ private HearingDeviceStatusDrawableInfo() {}
+
+ /**
+ * Returns the corresponding {@link StatusDrawableInfo} for the given {@link ConnectionStatus}.
+ */
+ public static StatusDrawableInfo get(@ConnectionStatus int status) {
+ return switch (status) {
+ case ConnectionStatus.DISCONNECTED -> DRAWABLE_DISCONNECTED_INFO;
+ case ConnectionStatus.CONNECTED -> DRAWABLE_CONNECTED_INFO;
+ case ConnectionStatus.ACTIVE -> DRAWABLE_ACTIVE_INFO;
+ // TODO: b/357882387 - Handle to show connecting or disconnecting status drawable
+ case ConnectionStatus.CONNECTING_OR_DISCONNECTING, ConnectionStatus.NO_DEVICE_BONDED ->
+ DRAWABLE_DEFAULT_INFO;
+ default -> DRAWABLE_DEFAULT_INFO;
+ };
+ }
+
+ /**
+ * A data class that holds the base drawable, indicator drawable and state description to
+ * represent hearing device connection status.
+ *
+ * @param baseDrawableId the base drawable id for the hearing device status
+ * @param indicatorDrawableId the indicator drawable id for the hearing device status
+ * @param stateDescriptionId the description for the hearing device status
+ */
+ public record StatusDrawableInfo(int baseDrawableId, int indicatorDrawableId,
+ int stateDescriptionId) {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
index 3e854b4dbaf8..2aea7d85e01a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -78,7 +78,13 @@ private fun toPopupChipModel(model: MediaControlChipModel?, context: Context): P
res = com.android.internal.R.drawable.ic_audio_media,
contentDescription = contentDescription,
),
+ hoverIcon =
+ Icon.Resource(
+ res = com.android.internal.R.drawable.ic_media_pause,
+ contentDescription = null,
+ ),
chipText = model.songName.toString(),
+ isToggled = false,
// TODO(b/385202114): Show a popup containing the media carousal when the chip is toggled.
onToggle = {},
// TODO(b/385202193): Add support for clicking on the icon on a media chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
index 0a6c4d0fd14f..e7e3d02ae4c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
@@ -38,7 +38,13 @@ sealed class PopupChipModel {
data class Shown(
override val chipId: PopupChipId,
+ /** Default icon displayed on the chip */
val icon: Icon,
+ /**
+ * Icon to be displayed if the chip is hovered. i.e. the mouse pointer is inside the bounds
+ * of the chip.
+ */
+ val hoverIcon: Icon,
val chipText: String,
val isToggled: Boolean = false,
val onToggle: () -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
new file mode 100644
index 000000000000..1a775d71983c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.featurepods.popups.ui.compose
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.hoverable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsHoveredAsState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.contentColorFor
+import androidx.compose.material3.ripple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
+
+/**
+ * A clickable chip that can show an anchored popup containing relevant system controls. The chip
+ * can show an icon that can have its own separate action distinct from its parent chip. Moreover,
+ * the chip can show text containing contextual information.
+ */
+@Composable
+fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifier) {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isHovered by interactionSource.collectIsHoveredAsState()
+ val isToggled = model.isToggled
+
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ modifier =
+ modifier
+ .hoverable(interactionSource = interactionSource)
+ .padding(vertical = 4.dp)
+ .widthIn(max = 120.dp)
+ .animateContentSize()
+ .clickable(onClick = { model.onToggle() }),
+ color =
+ if (isToggled) {
+ MaterialTheme.colorScheme.primaryContainer
+ } else {
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ },
+ ) {
+ Row(
+ modifier = Modifier.padding(start = 4.dp, end = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ val currentIcon = if (isHovered) model.hoverIcon else model.icon
+ val backgroundColor =
+ if (isToggled) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.primaryContainer
+ }
+
+ Icon(
+ icon = currentIcon,
+ modifier =
+ Modifier.background(color = backgroundColor, shape = CircleShape)
+ .clickable(
+ role = Role.Button,
+ onClick = model.onIconPressed,
+ indication = ripple(),
+ interactionSource = remember { MutableInteractionSource() },
+ )
+ .padding(2.dp)
+ .size(18.dp),
+ tint = contentColorFor(backgroundColor),
+ )
+
+ Text(
+ text = model.chipText,
+ style = MaterialTheme.typography.labelLarge,
+ softWrap = false,
+ overflow = TextOverflow.Ellipsis,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
index 56bbd74af1c2..d35674d8dd9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChipsContainer.kt
@@ -18,10 +18,11 @@ package com.android.systemui.statusbar.featurepods.popups.ui.compose
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
/** Container view that holds all right hand side chips in the status bar. */
@@ -29,9 +30,11 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM
fun StatusBarPopupChipsContainer(chips: List<PopupChipModel.Shown>, modifier: Modifier = Modifier) {
// TODO(b/385353140): Add padding and spacing for this container according to UX specs.
Box {
- Row(verticalAlignment = Alignment.CenterVertically) {
- // TODO(b/385352859): Show `StatusBarPopupChip` here instead of `Text` once it is ready.
- chips.forEach { chip -> Text(text = chip.chipText) }
+ Row(
+ modifier = Modifier.padding(horizontal = 8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ chips.forEach { chip -> StatusBarPopupChip(chip) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5a52c379d0d6..7e3d0043b91a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -3685,6 +3685,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren && (!mIsMinimized || wasExpanded)) {
nowExpanded = mGroupExpansionManager.isGroupExpanded(mEntry);
}
+ // Note: nowExpanded is going to be true here on the first expansion of minimized groups,
+ // even though the group itself is not expanded. Use mGroupExpansionManager to get the real
+ // group expansion if needed.
if (nowExpanded != wasExpanded) {
updateShelfIconColor();
if (mLogger != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 70e27a981b49..7c44eae6c0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Flags.notificationsRedesignTemplates;
+
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_SENSITIVE_CONTENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -479,15 +481,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
logger.logAsyncTaskProgress(entryForLogging,
"creating low-priority group summary remote view");
result.mNewMinimizedGroupHeaderView =
- builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ builder.makeLowPriorityContentView(/* useRegularSubtext = */ true,
+ /* highlightExpander = */ notificationsRedesignTemplates());
}
}
setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
result.packageContext = packageContext;
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
- false /* showingPublic */);
+ /* showingPublic = */ false);
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- true /* showingPublic */);
+ /* showingPublic = */ true);
return result;
});
@@ -1136,7 +1139,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private static RemoteViews createContentView(Notification.Builder builder,
boolean isMinimized, boolean useLarge) {
if (isMinimized) {
- return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
+ return builder.makeLowPriorityContentView(/* useRegularSubtext = */ false,
+ /* highlightExpander = */ false);
}
return builder.createContentView(useLarge);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index c619b17f1ad8..ae9b69c8f6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row
import android.annotation.SuppressLint
+import android.app.Flags.notificationsRedesignTemplates
import android.app.Notification
import android.app.Notification.MessagingStyle
import android.content.Context
@@ -887,7 +888,10 @@ constructor(
entryForLogging,
"creating low-priority group summary remote view",
)
- builder.makeLowPriorityContentView(true /* useRegularSubtext */)
+ builder.makeLowPriorityContentView(
+ /* useRegularSubtext = */ true,
+ /* highlightExpander = */ notificationsRedesignTemplates(),
+ )
} else null
NewRemoteViews(
contracted = contracted,
@@ -1657,7 +1661,10 @@ constructor(
useLarge: Boolean,
): RemoteViews {
return if (isMinimized) {
- builder.makeLowPriorityContentView(false /* useRegularSubtext */)
+ builder.makeLowPriorityContentView(
+ /* useRegularSubtext = */ false,
+ /* highlightExpander = */ false,
+ )
} else builder.createContentView(useLarge)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 9fb7fad52bb6..e477c7430262 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -30,7 +30,6 @@ import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -93,9 +92,16 @@ public class NotificationChildrenContainer extends ViewGroup
private int mChildPadding;
private int mDividerHeight;
private float mDividerAlpha;
- private int mNotificationHeaderMargin;
- private int mNotificationTopPadding;
+ private int mHeaderHeight;
+ /** Margin needed at the top in the collapsed group, to allow space for the header text. */
+ private int mCollapsedHeaderMargin;
+ /**
+ * Spacing needed in addition to {@link this#mCollapsedHeaderMargin} when the group is expanded,
+ * to accommodate the full header. It doesn't include the spacing needed for the first divider.
+ */
+ private int mAdditionalExpandedHeaderMargin;
+
private float mCollapsedBottomPadding;
private boolean mChildrenExpanded;
private ExpandableNotificationRow mContainingNotification;
@@ -105,7 +111,6 @@ public class NotificationChildrenContainer extends ViewGroup
private boolean mUserLocked;
private int mActualHeight;
private boolean mNeverAppliedGroupState;
- private int mHeaderHeight;
/**
* Whether or not individual notifications that are part of this container will have shadows.
@@ -168,15 +173,18 @@ public class NotificationChildrenContainer extends ViewGroup
mDividerHeight = res.getDimensionPixelOffset(
R.dimen.notification_children_container_divider_height);
mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
- mNotificationHeaderMargin = notificationsRedesignTemplates()
- ? Notification.Builder.getContentMarginTop(getContext(),
- R.dimen.notification_2025_children_container_margin_top)
- : res.getDimensionPixelOffset(R.dimen.notification_children_container_margin_top);
- mNotificationTopPadding = res.getDimensionPixelOffset(
- R.dimen.notification_children_container_top_padding);
- mHeaderHeight = notificationsRedesignTemplates()
- ? res.getDimensionPixelSize(R.dimen.notification_2025_header_height)
- : mNotificationHeaderMargin + mNotificationTopPadding;
+ if (notificationsRedesignTemplates()) {
+ mHeaderHeight = res.getDimensionPixelSize(R.dimen.notification_2025_header_height);
+ mCollapsedHeaderMargin = Notification.Builder.getContentMarginTop(getContext(),
+ R.dimen.notification_2025_children_container_margin_top);
+ mAdditionalExpandedHeaderMargin = mHeaderHeight - mCollapsedHeaderMargin;
+ } else {
+ mCollapsedHeaderMargin = res.getDimensionPixelOffset(
+ R.dimen.notification_children_container_margin_top);
+ mAdditionalExpandedHeaderMargin = res.getDimensionPixelOffset(
+ R.dimen.notification_children_container_top_padding);
+ mHeaderHeight = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin;
+ }
mCollapsedBottomPadding = res.getDimensionPixelOffset(
R.dimen.notification_children_collapsed_bottom_padding);
mEnableShadowOnChildNotifications =
@@ -189,7 +197,7 @@ public class NotificationChildrenContainer extends ViewGroup
res.getBoolean(R.bool.config_hideDividersDuringExpand);
mTranslationForHeader = res.getDimensionPixelOffset(
com.android.internal.R.dimen.notification_content_margin)
- - mNotificationHeaderMargin;
+ - mCollapsedHeaderMargin;
mHybridGroupManager.initDimens();
mMinSingleLineHeight = getResources().getDimensionPixelSize(
R.dimen.conversation_single_line_face_pile_size);
@@ -251,7 +259,7 @@ public class NotificationChildrenContainer extends ViewGroup
newHeightSpec);
}
int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
- int height = mNotificationHeaderMargin + mNotificationTopPadding;
+ int height = mCollapsedHeaderMargin + mAdditionalExpandedHeaderMargin;
int childCount =
Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
@@ -560,7 +568,8 @@ public class NotificationChildrenContainer extends ViewGroup
builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
}
- header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ header = builder.makeLowPriorityContentView(true /* useRegularSubtext */,
+ notificationsRedesignTemplates() /* highlightExpander */);
if (mMinimizedGroupHeader == null) {
mMinimizedGroupHeader = (NotificationHeaderView) header.apply(getContext(),
this);
@@ -704,7 +713,7 @@ public class NotificationChildrenContainer extends ViewGroup
return mMinimizedGroupHeader.getHeight();
}
}
- int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
+ int intrinsicHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation;
int visibleChildren = 0;
int childCount = mAttachedChildren.size();
boolean firstChild = true;
@@ -728,11 +737,11 @@ public class NotificationChildrenContainer extends ViewGroup
if (mUserLocked) {
intrinsicHeight += NotificationUtils.interpolate(
0,
- mNotificationTopPadding + mDividerHeight,
+ mAdditionalExpandedHeaderMargin + mDividerHeight,
expandFactor);
} else {
intrinsicHeight += childrenExpanded
- ? mNotificationTopPadding + mDividerHeight
+ ? mAdditionalExpandedHeaderMargin + mDividerHeight
: 0;
}
firstChild = false;
@@ -757,7 +766,7 @@ public class NotificationChildrenContainer extends ViewGroup
*/
public void updateState(ExpandableViewState parentState) {
int childCount = mAttachedChildren.size();
- int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
+ int yPosition = mCollapsedHeaderMargin + mCurrentHeaderTranslation;
boolean firstChild = true;
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
int lastVisibleIndex = maxAllowedVisibleChildren - 1;
@@ -785,10 +794,12 @@ public class NotificationChildrenContainer extends ViewGroup
if (expandingToExpandedGroup) {
yPosition += NotificationUtils.interpolate(
0,
- mNotificationTopPadding + mDividerHeight,
+ mAdditionalExpandedHeaderMargin + mDividerHeight,
expandFactor);
} else {
- yPosition += mChildrenExpanded ? mNotificationTopPadding + mDividerHeight : 0;
+ yPosition += mChildrenExpanded
+ ? mAdditionalExpandedHeaderMargin + mDividerHeight
+ : 0;
}
firstChild = false;
}
@@ -851,7 +862,7 @@ public class NotificationChildrenContainer extends ViewGroup
}
} else {
mGroupOverFlowState.setYTranslation(
- mGroupOverFlowState.getYTranslation() + mNotificationHeaderMargin);
+ mGroupOverFlowState.getYTranslation() + mCollapsedHeaderMargin);
mGroupOverFlowState.setAlpha(0.0f);
}
}
@@ -1147,10 +1158,12 @@ public class NotificationChildrenContainer extends ViewGroup
return mContainingNotification;
}
+ @Nullable
public NotificationViewWrapper getNotificationViewWrapper() {
return mGroupHeaderWrapper;
}
+ @Nullable
public NotificationViewWrapper getMinimizedGroupHeaderWrapper() {
return mMinimizedGroupHeaderWrapper;
}
@@ -1160,10 +1173,12 @@ public class NotificationChildrenContainer extends ViewGroup
return mCurrentHeader;
}
+ @Nullable
public NotificationHeaderView getGroupHeader() {
return mGroupHeader;
}
+ @Nullable
public NotificationHeaderView getMinimizedNotificationHeader() {
return mMinimizedGroupHeader;
}
@@ -1300,8 +1315,8 @@ public class NotificationChildrenContainer extends ViewGroup
return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true
/* likeHighPriority */);
}
- int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
- + mNotificationTopPadding;
+ int maxContentHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation
+ + mAdditionalExpandedHeaderMargin;
int visibleChildren = 0;
int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
@@ -1363,8 +1378,8 @@ public class NotificationChildrenContainer extends ViewGroup
}
private int getVisibleChildrenExpandHeight() {
- int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
- + mNotificationTopPadding + mDividerHeight;
+ int intrinsicHeight = mCollapsedHeaderMargin + mCurrentHeaderTranslation
+ + mAdditionalExpandedHeaderMargin + mDividerHeight;
int visibleChildren = 0;
int childCount = mAttachedChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
@@ -1429,7 +1444,7 @@ public class NotificationChildrenContainer extends ViewGroup
}
return mMinimizedGroupHeader.getHeight();
}
- int minExpandHeight = mNotificationHeaderMargin + headerTranslation;
+ int minExpandHeight = mCollapsedHeaderMargin + headerTranslation;
int visibleChildren = 0;
boolean firstChild = true;
int childCount = mAttachedChildren.size();
@@ -1517,18 +1532,13 @@ public class NotificationChildrenContainer extends ViewGroup
// The overflow number is not used, so its color is irrelevant; skip this
return;
}
- int color = mContainingNotification.getNotificationColor();
- Resources.Theme theme = new ContextThemeWrapper(mContext,
- com.android.internal.R.style.Theme_DeviceDefault_DayNight).getTheme();
-
- color = mContext.getColor(com.android.internal.R.color.materialColorPrimary);
-
+ int color = mContext.getColor(com.android.internal.R.color.materialColorPrimary);
mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, color);
}
public int getPositionInLinearLayout(View childInGroup) {
- int position = mNotificationHeaderMargin + mCurrentHeaderTranslation
- + mNotificationTopPadding;
+ int position = mCollapsedHeaderMargin + mCurrentHeaderTranslation
+ + mAdditionalExpandedHeaderMargin;
for (int i = 0; i < mAttachedChildren.size(); i++) {
ExpandableNotificationRow child = mAttachedChildren.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 50e5a2352f43..9766f9e0ab1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack;
+import static android.app.Flags.notificationsRedesignTemplates;
import static android.os.Trace.TRACE_TAG_APP;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_UP;
@@ -62,6 +63,7 @@ import android.view.DisplayCutout;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.NotificationHeaderView;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
@@ -6849,15 +6851,29 @@ public class NotificationStackScrollLayout
mExpandedGroupView = changedRow;
mNeedsAnimation = true;
}
+
changedRow.setChildrenExpanded(expanded);
onChildHeightChanged(changedRow, false /* needsAnimation */);
+ updateGroupHeaderAlignment(changedRow, expanded);
- runAfterAnimationFinished(new Runnable() {
- @Override
- public void run() {
- changedRow.onFinishedExpansionChange();
- }
- });
+ runAfterAnimationFinished(changedRow::onFinishedExpansionChange);
+ }
+
+ private void updateGroupHeaderAlignment(ExpandableNotificationRow row, boolean expanded) {
+ if (!notificationsRedesignTemplates()) {
+ return;
+ }
+
+ NotificationChildrenContainer childrenContainer = row.getChildrenContainer();
+ if (childrenContainer == null) {
+ Log.wtf(TAG, "Tried to update group header alignment for something that's "
+ + "not a group; key = " + row.getEntry().getKey());
+ return;
+ }
+ NotificationHeaderView header = childrenContainer.getGroupHeader();
+ if (header != null) {
+ header.centerTopLine(expanded);
+ }
}
private final ExpandHelper.Callback mExpandHelperCallback = new ExpandHelper.Callback() {
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 09440e0e997d..600b124ffbf6 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -3227,6 +3227,12 @@ public class AccountManagerService
"the type and name should not be empty");
return;
}
+ if (!type.equals(mAccountType)) {
+ onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+ "incorrect account type");
+ return;
+ }
+
Account resultAccount = new Account(name, type);
if (!customTokens) {
saveAuthTokenToDatabase(
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 6e09a84e0f8c..e4e53f4124f3 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -1434,7 +1434,7 @@ public class ContentProviderHelper {
}
}
}
- } catch (RemoteException ignored) {
+ } catch (RemoteException|SecurityException ignored) {
}
});
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 165f9d3340e3..f91b030dd1ba 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -256,7 +256,9 @@ public class ContextHubService extends IContextHubService.Stub {
public void handleServiceRestart() {
Log.i(TAG, "Recovering from Context Hub HAL restart...");
initExistingCallbacks();
- mHubInfoRegistry.onHalRestart();
+ if (mHubInfoRegistry != null) {
+ mHubInfoRegistry.onHalRestart();
+ }
resetSettings();
if (Flags.reconnectHostEndpointsAfterHalRestart()) {
mClientManager.forEachClientOfHub(mContextHubId,
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e6737a485af..6c0d8ad7264d 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2197,7 +2197,7 @@ class MediaRouter2ServiceImpl {
mRouter.notifyRouterRegistered(
getVisibleRoutes(currentRoutes), currentSystemSessionInfo);
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify router registered. Router probably died.", ex);
+ logRemoteException("notifyRegistered", ex);
}
}
@@ -2212,7 +2212,7 @@ class MediaRouter2ServiceImpl {
try {
mRouter.notifyRoutesUpdated(getVisibleRoutes(routes));
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify routes updated. Router probably died.", ex);
+ logRemoteException("notifyRoutesUpdated", ex);
}
}
@@ -2221,11 +2221,7 @@ class MediaRouter2ServiceImpl {
mRouter.notifySessionCreated(
requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
} catch (RemoteException ex) {
- Slog.w(
- TAG,
- "Failed to notify router of the session creation."
- + " Router probably died.",
- ex);
+ logRemoteException("notifySessionCreated", ex);
}
}
@@ -2238,11 +2234,7 @@ class MediaRouter2ServiceImpl {
try {
mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null);
} catch (RemoteException ex) {
- Slog.w(
- TAG,
- "Failed to notify router of the session creation failure."
- + " Router probably died.",
- ex);
+ logRemoteException("notifySessionCreationFailed", ex);
}
}
@@ -2253,10 +2245,7 @@ class MediaRouter2ServiceImpl {
try {
mRouter.notifySessionReleased(sessionInfo);
} catch (RemoteException ex) {
- Slog.w(
- TAG,
- "Failed to notify router of the session release. Router probably died.",
- ex);
+ logRemoteException("notifySessionReleased", ex);
}
}
@@ -2284,11 +2273,7 @@ class MediaRouter2ServiceImpl {
}
mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
} catch (RemoteException ex) {
- Slog.w(
- TAG,
- "getSessionHintsForCreatingSessionOnHandler: "
- + "Failed to request. Router probably died.",
- ex);
+ logRemoteException("requestCreateSessionByManager", ex);
managerRecord.notifyRequestFailed(
toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
}
@@ -2303,7 +2288,7 @@ class MediaRouter2ServiceImpl {
try {
mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
+ logRemoteException("notifySessionInfoChanged", ex);
}
}
@@ -2362,6 +2347,22 @@ class MediaRouter2ServiceImpl {
}
return false;
}
+
+ /** Logs a {@link RemoteException} occurred during the execution of {@code operation}. */
+ private void logRemoteException(String operation, RemoteException exception) {
+ String message =
+ TextUtils.formatSimple(
+ "%s failed for %s due to %s",
+ operation, getDebugString(), exception.toString());
+ Slog.w(TAG, message);
+ }
+
+ /** Returns a human readable representation of this router record for logging purposes. */
+ private String getDebugString() {
+ return TextUtils.formatSimple(
+ "Router %s (id=%d,pid=%d,userId=%d,uid=%d)",
+ mPackageName, mRouterId, mPid, mUserRecord.mUserId, mUid);
+ }
}
final class ManagerRecord implements IBinder.DeathRecipient {
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 755d4c8c9fc5..6dd7d35856df 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -348,12 +348,14 @@ class PinnedTaskController {
* Notifies listeners that the PIP needs to be adjusted for the IME.
*/
private void notifyImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- if (mPinnedTaskListener != null) {
- try {
- mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering bounds changed event.", e);
- }
+ if (mPinnedTaskListener == null) {
+ return;
+ }
+
+ try {
+ mPinnedTaskListener.onImeVisibilityChanged(imeVisible, imeHeight);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering ime visibility changed event.", e);
}
}
@@ -361,15 +363,14 @@ class PinnedTaskController {
* Notifies listeners that the PIP movement bounds have changed.
*/
private void notifyMovementBoundsChanged(boolean fromImeAdjustment) {
- synchronized (mService.mGlobalLock) {
- if (mPinnedTaskListener == null) {
- return;
- }
- try {
- mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering actions changed event.", e);
- }
+ if (mPinnedTaskListener == null) {
+ return;
+ }
+
+ try {
+ mPinnedTaskListener.onMovementBoundsChanged(fromImeAdjustment);
+ } catch (RemoteException e) {
+ Slog.e(TAG_WM, "Error delivering movement bounds changed event.", e);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3634bc987d0a..d962b6b11187 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -845,7 +845,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
*/
void positionTaskBehindHome(Task task) {
final Task home = getOrCreateRootHomeTask();
- final WindowContainer homeParent = home.getParent();
+ final WindowContainer homeParent = home != null ? home.getParent() : null;
final Task homeParentTask = homeParent != null ? homeParent.asTask() : null;
if (homeParentTask == null) {
// reparent throws if parent didn't change...
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 367adc355738..b90a2111ac84 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1339,14 +1339,24 @@ class TaskFragment extends WindowContainer<WindowContainer> {
mTmpRect.set(getBounds());
for (int j = adjacentTaskFragments.size() - 1; j >= 0; --j) {
final TaskFragment taskFragment = adjacentTaskFragments.get(j);
- final TaskFragment adjacentTaskFragment =
- taskFragment.mAdjacentTaskFragment;
- if (adjacentTaskFragment == this) {
+ if (taskFragment.isAdjacentTo(this)) {
continue;
}
- if (mTmpRect.intersect(taskFragment.getBounds())
- || mTmpRect.intersect(adjacentTaskFragment.getBounds())) {
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ final boolean isOccluding = mTmpRect.intersect(taskFragment.getBounds())
+ || taskFragment.forOtherAdjacentTaskFragments(adjacentTf -> {
+ return mTmpRect.intersect(adjacentTf.getBounds());
+ });
+ if (isOccluding) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
+ } else {
+ final TaskFragment adjacentTaskFragment =
+ taskFragment.mAdjacentTaskFragment;
+ if (mTmpRect.intersect(taskFragment.getBounds())
+ || mTmpRect.intersect(adjacentTaskFragment.getBounds())) {
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
}
}
}
@@ -1374,20 +1384,38 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
final TaskFragment otherTaskFrag = other.asTaskFragment();
- if (otherTaskFrag != null && otherTaskFrag.mAdjacentTaskFragment != null) {
- if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
- if (otherTaskFrag.isTranslucent(starting)
- || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) {
- // Can be visible behind a translucent adjacent TaskFragments.
- gotTranslucentFullscreen = true;
- gotTranslucentAdjacent = true;
- continue;
+ if (otherTaskFrag != null && otherTaskFrag.hasAdjacentTaskFragment()) {
+ if (Flags.allowMultipleAdjacentTaskFragments()) {
+ final boolean hasTraversedAdj = otherTaskFrag.forOtherAdjacentTaskFragments(
+ adjacentTaskFragments::contains);
+ if (hasTraversedAdj) {
+ final boolean isTranslucent = otherTaskFrag.isTranslucent(starting)
+ || otherTaskFrag.forOtherAdjacentTaskFragments(adjacentTf -> {
+ return adjacentTf.isTranslucent(starting);
+ });
+ if (isTranslucent) {
+ // Can be visible behind a translucent adjacent TaskFragments.
+ gotTranslucentFullscreen = true;
+ gotTranslucentAdjacent = true;
+ continue;
+ }
+ // Can not be visible behind adjacent TaskFragments.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
}
- // Can not be visible behind adjacent TaskFragments.
- return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
} else {
- adjacentTaskFragments.add(otherTaskFrag);
+ if (adjacentTaskFragments.contains(otherTaskFrag.mAdjacentTaskFragment)) {
+ if (otherTaskFrag.isTranslucent(starting)
+ || otherTaskFrag.mAdjacentTaskFragment.isTranslucent(starting)) {
+ // Can be visible behind a translucent adjacent TaskFragments.
+ gotTranslucentFullscreen = true;
+ gotTranslucentAdjacent = true;
+ continue;
+ }
+ // Can not be visible behind adjacent TaskFragments.
+ return TASK_FRAGMENT_VISIBILITY_INVISIBLE;
+ }
}
+ adjacentTaskFragments.add(otherTaskFrag);
}
}
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 2ebede39504e..87ea5db57db4 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -100,3 +100,72 @@ sh_test_host {
unit_test: true,
},
}
+
+java_library_host {
+ name: "systemfeatures-errorprone-lib",
+ srcs: [
+ ":systemfeatures-gen-metadata-srcs",
+ "errorprone/java/**/*.java",
+ ],
+ static_libs: [
+ "//external/error_prone:error_prone_core",
+ "guava",
+ "jsr305",
+ ],
+ libs: [
+ "//external/auto:auto_service_annotations",
+ ],
+ javacflags: [
+ // These exports are needed because this errorprone plugin access some private classes
+ // of the java compiler.
+ "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
+ plugins: [
+ "//external/auto:auto_service_plugin",
+ ],
+}
+
+java_plugin {
+ name: "systemfeatures-errorprone",
+ static_libs: ["systemfeatures-errorprone-lib"],
+}
+
+java_test_host {
+ name: "systemfeatures-errorprone-tests",
+ srcs: [
+ "errorprone/tests/java/**/*.java",
+ ],
+ java_resource_dirs: ["tests/src"],
+ java_resources: [
+ ":systemfeatures-errorprone-tests-data",
+ ],
+ static_libs: [
+ "compile-testing-prebuilt",
+ "error_prone_test_helpers",
+ "framework-annotations-lib",
+ "hamcrest",
+ "hamcrest-library",
+ "junit",
+ "systemfeatures-errorprone-lib",
+ "truth",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
+
+java_system_features_srcs {
+ name: "systemfeatures-gen-metadata-srcs",
+ full_class_name: "com.android.systemfeatures.RoSystemFeaturesMetadata",
+ metadata_only: true,
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "systemfeatures-errorprone-tests-data",
+ path: "tests/src",
+ srcs: ["tests/src/android/**/*.java"],
+ visibility: ["//visibility:private"],
+}
diff --git a/tools/systemfeatures/README.md b/tools/systemfeatures/README.md
index 5836f81e5fd3..b1fec1a34723 100644
--- a/tools/systemfeatures/README.md
+++ b/tools/systemfeatures/README.md
@@ -4,8 +4,110 @@
System features exposed from `PackageManager` are defined and aggregated as
`<feature>` xml attributes across various partitions, and are currently queried
-at runtime through the framework. This directory contains tooling that will
-support *build-time* queries of select system features, enabling optimizations
+at runtime through the framework. This directory contains tooling that supports
+*build-time* queries of select system features, enabling optimizations
like code stripping and conditionally dependencies when so configured.
-### TODO(b/203143243): Expand readme after landing codegen.
+### System Feature Codegen
+
+As not all system features can be fully specified or defined at build time (e.g.
+updatable partitisions and apex modules can change/remove such features), we
+use a conditional, build flag approach that allows a given device to customize
+the subset of build-time defined system features that are immutable and cannot
+be updated.
+
+#### Build Flags
+
+System features that can be fixed at build-time are declared in a common
+location, `build/release/flag_declarations/`. These have the form
+`RELEASE_SYSTEM_FEATURE_${X}`, where `${X}` corresponds to a feature defined in
+`PackageManager`, e.g., `TELEVISION` or `WATCH`.
+
+Build flag values can then be defined per device (or form factor), where such
+values either indicate the existence/version of the system feature, or that the
+feature is unavailable, e.g., for TV, we could define these build flag values:
+```
+name: "RELEASE_SYSTEM_FEATURE_TELEVISION"
+value: {
+ string_value: "0" # Feature version = 0
+}
+```
+```
+name: "RELEASE_SYSTEM_FEATURE_WATCH"
+value: {
+ string_value: "UNAVAILABLE"
+}
+```
+
+See also [SystemFeaturesGenerator](src/com/android/systemfeatures/SystemFeaturesGenerator.kt)
+for more details.
+
+#### Runtime Queries
+
+Each declared build flag system feature is routed into codegen, generating a
+getter API in the internal class, `com.android.internal.pm.RoSystemFeatures`:
+```
+class RoSystemFeatures {
+ ...
+ public static boolean hasFeatureX(Context context);
+ ...
+}
+```
+By default, these queries simply fall back to the usual
+`PackageManager.hasSystemFeature(...)` runtime queries. However, if a device
+defines these features via build flags, the generated code will add annotations
+indicating fixed value for this query, and adjust the generated code to return
+the value directly. This in turn enables build-time stripping and optimization.
+
+> **_NOTE:_** Any build-time defined system features will also be implicitly
+used to accelerate calls to `PackageManager.hasSystemFeature(...)` for the
+feature, avoiding binder calls when possible.
+
+#### Lint
+
+A new `ErrorProne` rule is introduced to assist with migration and maintenance
+of codegen APIs for build-time defined system features. This is defined in the
+`systemfeatures-errorprone` build rule, which can be added to any Java target's
+`plugins` list.
+
+// TODO(b/203143243): Add plugin to key system targets after initial migration.
+
+1) Add the plugin dependency to a given `${TARGET}`:
+```
+java_library {
+ name: "${TARGET}",
+ plugins: ["systemfeatures-errorprone"],
+}
+```
+2) Run locally:
+```
+RUN_ERROR_PRONE=true m ${TARGET}
+```
+3) (Optional) Update the target rule to generate in-place patch files:
+```
+java_library {
+ name: "${TARGET}",
+ plugins: ["systemfeatures-errorprone"],
+ // DO NOT SUBMIT: GENERATE IN-PLACE PATCH FILES
+ errorprone: {
+ javacflags: [
+ "-XepPatchChecks:RoSystemFeaturesChecker",
+ "-XepPatchLocation:IN_PLACE",
+ ],
+ }
+ ...
+}
+```
+```
+RUN_ERROR_PRONE=true m ${TARGET}
+```
+
+See also [RoSystemFeaturesChecker](errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java)
+for more details.
+
+> **_NOTE:_** Not all system feature queries or targets need or should be
+migrated. Only system features that are explicitly declared with build flags,
+and only targets that are built with the platform (i.e., not updatable), are
+candidates for this linting and migration, e.g., SystemUI, System Server, etc...
+
+// TODO(b/203143243): Wrap the in-place lint updates with a simple script for convenience.
diff --git a/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java
new file mode 100644
index 000000000000..78123774205a
--- /dev/null
+++ b/tools/systemfeatures/errorprone/java/com/android/systemfeatures/errorprone/RoSystemFeaturesChecker.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures.errorprone;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+
+import com.android.systemfeatures.RoSystemFeaturesMetadata;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.fixes.SuggestedFix;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.matchers.Matcher;
+import com.google.errorprone.matchers.Matchers;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.tools.javac.code.Symbol;
+
+@AutoService(BugChecker.class)
+@BugPattern(
+ name = "RoSystemFeaturesChecker",
+ summary = "Use RoSystemFeature instead of PackageManager.hasSystemFeature",
+ explanation =
+ "Directly invoking `PackageManager.hasSystemFeature` is less efficient than using"
+ + " the `RoSystemFeatures` helper class. This check flags invocations like"
+ + " `context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FOO)`"
+ + " and suggests replacing them with"
+ + " `com.android.internal.pm.RoSystemFeatures.hasFeatureFoo(context)`.",
+ severity = WARNING)
+public class RoSystemFeaturesChecker extends BugChecker
+ implements BugChecker.MethodInvocationTreeMatcher {
+
+ private static final String PACKAGE_MANAGER_CLASS = "android.content.pm.PackageManager";
+ private static final String CONTEXT_CLASS = "android.content.Context";
+ private static final String RO_SYSTEM_FEATURE_SIMPLE_CLASS = "RoSystemFeatures";
+ private static final String RO_SYSTEM_FEATURE_CLASS =
+ "com.android.internal.pm." + RO_SYSTEM_FEATURE_SIMPLE_CLASS;
+ private static final String GET_PACKAGE_MANAGER_METHOD = "getPackageManager";
+ private static final String HAS_SYSTEM_FEATURE_METHOD = "hasSystemFeature";
+ private static final String FEATURE_PREFIX = "FEATURE_";
+
+ private static final Matcher<ExpressionTree> HAS_SYSTEM_FEATURE_MATCHER =
+ Matchers.instanceMethod()
+ .onDescendantOf(PACKAGE_MANAGER_CLASS)
+ .named(HAS_SYSTEM_FEATURE_METHOD)
+ .withParameters(String.class.getName());
+
+ private static final Matcher<ExpressionTree> GET_PACKAGE_MANAGER_MATCHER =
+ Matchers.instanceMethod()
+ .onDescendantOf(CONTEXT_CLASS)
+ .named(GET_PACKAGE_MANAGER_METHOD);
+
+ @Override
+ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+ if (!HAS_SYSTEM_FEATURE_MATCHER.matches(tree, state)) {
+ return Description.NO_MATCH;
+ }
+
+ // Check if the PackageManager was obtained from a Context instance.
+ ExpressionTree packageManager = ASTHelpers.getReceiver(tree);
+ if (!GET_PACKAGE_MANAGER_MATCHER.matches(packageManager, state)) {
+ return Description.NO_MATCH;
+ }
+
+ // Get the feature argument and check if it's a PackageManager.FEATURE_X constant.
+ ExpressionTree feature = tree.getArguments().isEmpty() ? null : tree.getArguments().get(0);
+ Symbol featureSymbol = ASTHelpers.getSymbol(feature);
+ if (featureSymbol == null
+ || !featureSymbol.isStatic()
+ || !featureSymbol.getSimpleName().toString().startsWith(FEATURE_PREFIX)
+ || ASTHelpers.enclosingClass(featureSymbol) == null
+ || !ASTHelpers.enclosingClass(featureSymbol)
+ .getQualifiedName()
+ .contentEquals(PACKAGE_MANAGER_CLASS)) {
+ return Description.NO_MATCH;
+ }
+
+ // Check if the feature argument is part of the RoSystemFeatures API surface.
+ String featureName = featureSymbol.getSimpleName().toString();
+ String methodName = RoSystemFeaturesMetadata.getMethodNameForFeatureName(featureName);
+ if (methodName == null) {
+ return Description.NO_MATCH;
+ }
+
+ // Generate the appropriate fix.
+ String replacement =
+ String.format(
+ "%s.%s(%s)",
+ RO_SYSTEM_FEATURE_SIMPLE_CLASS,
+ methodName,
+ state.getSourceForNode(ASTHelpers.getReceiver(packageManager)));
+ // Note that ErrorProne doesn't offer a seamless way of removing the `PackageManager` import
+ // if unused after fix application, so for now we only offer best effort import suggestions.
+ SuggestedFix fix =
+ SuggestedFix.builder()
+ .replace(tree, replacement)
+ .addImport(RO_SYSTEM_FEATURE_CLASS)
+ .removeStaticImport(PACKAGE_MANAGER_CLASS + "." + featureName)
+ .build();
+ return describeMatch(tree, fix);
+ }
+}
diff --git a/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java
new file mode 100644
index 000000000000..c517b2495ee4
--- /dev/null
+++ b/tools/systemfeatures/errorprone/tests/java/com/android/systemfeatures/errorprone/RoSystemFeaturesCheckerTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemfeatures.errorprone;
+
+import com.google.errorprone.BugCheckerRefactoringTestHelper;
+import com.google.errorprone.CompilationTestHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class RoSystemFeaturesCheckerTest {
+ private BugCheckerRefactoringTestHelper mRefactoringHelper;
+ private CompilationTestHelper mCompilationHelper;
+
+ @Before
+ public void setUp() {
+ mCompilationHelper =
+ CompilationTestHelper.newInstance(RoSystemFeaturesChecker.class, getClass());
+ mRefactoringHelper =
+ BugCheckerRefactoringTestHelper.newInstance(
+ RoSystemFeaturesChecker.class, getClass());
+ }
+
+ @Test
+ public void testNoDiagnostic() {
+ mCompilationHelper
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/pm/PackageManager.java")
+ .addSourceLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ void test(Context context) {
+ boolean hasCustomFeature = context.getPackageManager()
+ .hasSystemFeature("my.custom.feature");
+ boolean hasNonAnnotatedFeature = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_NOT_ANNOTATED);
+ boolean hasNonRoApiFeature = context.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_NOT_IN_RO_FEATURE_API);
+ }
+ }
+ """)
+ .doTest();
+ }
+
+ @Test
+ public void testDiagnostic() {
+ mCompilationHelper
+ .addSourceFile("/android/content/Context.java")
+ .addSourceFile("/android/content/pm/PackageManager.java")
+ .addSourceLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ void test(Context context) {
+ boolean hasFeature = context.getPackageManager()
+ // BUG: Diagnostic contains:
+ .hasSystemFeature(PackageManager.FEATURE_PC);
+ }
+ }
+ """)
+ .doTest();
+ }
+
+ @Test
+ public void testFix() {
+ mRefactoringHelper
+ .addInputLines("Example.java",
+ """
+ import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ public class Example {
+ static class CustomContext extends Context {};
+ private CustomContext mContext;
+ void test(Context context) {
+ boolean hasPc = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_PC);
+ boolean hasWatch = context.getPackageManager()
+ .hasSystemFeature(FEATURE_WATCH);
+ }
+ }
+ """)
+ .addOutputLines("Example.java",
+ """
+ import android.content.Context;
+ import android.content.pm.PackageManager;
+ import com.android.internal.pm.RoSystemFeatures;
+ public class Example {
+ static class CustomContext extends Context {};
+ private CustomContext mContext;
+ void test(Context context) {
+ boolean hasPc = RoSystemFeatures.hasFeaturePc(mContext);
+ boolean hasWatch = RoSystemFeatures.hasFeatureWatch(context);
+ }
+ }
+ """)
+ // Don't try compiling the output, as it requires pulling in the full set of code
+ // dependencies.
+ .allowBreakingChanges()
+ .doTest();
+ }
+}
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index f260e2733843..ea660b013893 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -53,11 +53,20 @@ import javax.lang.model.element.Modifier
* public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures();
* }
* </pre>
+ *
+ * <p> If `--metadata-only=true` is set, the resulting class would simply be:
+ * <pre>
+ * package com.foo;
+ * public final class RoSystemFeatures {
+ * public static String getMethodNameForFeatureName(String featureName);
+ * }
+ * </pre>
*/
object SystemFeaturesGenerator {
private const val FEATURE_ARG = "--feature="
private const val FEATURE_APIS_ARG = "--feature-apis="
private const val READONLY_ARG = "--readonly="
+ private const val METADATA_ONLY_ARG = "--metadata-only="
private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
private val CONTEXT_CLASS = ClassName.get("android.content", "Context")
private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo")
@@ -84,6 +93,8 @@ object SystemFeaturesGenerator {
println(" runtime passthrough API will be generated, regardless")
println(" of the `--readonly` flag. This allows decoupling the")
println(" API surface from variations in device feature sets.")
+ println(" --metadata-only=true|false Whether to simply output metadata about the")
+ println(" generated API surface.")
}
/** Main entrypoint for build-time system feature codegen. */
@@ -106,6 +117,7 @@ object SystemFeaturesGenerator {
}
var readonly = false
+ var metadataOnly = false
var outputClassName: ClassName? = null
val featureArgs = mutableListOf<FeatureInfo>()
// We could just as easily hardcode this list, as the static API surface should change
@@ -115,6 +127,8 @@ object SystemFeaturesGenerator {
when {
arg.startsWith(READONLY_ARG) ->
readonly = arg.substring(READONLY_ARG.length).toBoolean()
+ arg.startsWith(METADATA_ONLY_ARG) ->
+ metadataOnly = arg.substring(METADATA_ONLY_ARG.length).toBoolean()
arg.startsWith(FEATURE_ARG) -> {
featureArgs.add(parseFeatureArg(arg))
}
@@ -155,9 +169,13 @@ object SystemFeaturesGenerator {
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addJavadoc("@hide")
- addFeatureMethodsToClass(classBuilder, features.values)
- addMaybeFeatureMethodToClass(classBuilder, features.values)
- addGetFeaturesMethodToClass(classBuilder, features.values)
+ if (metadataOnly) {
+ addMetadataMethodToClass(classBuilder, features.values)
+ } else {
+ addFeatureMethodsToClass(classBuilder, features.values)
+ addMaybeFeatureMethodToClass(classBuilder, features.values)
+ addGetFeaturesMethodToClass(classBuilder, features.values)
+ }
// TODO(b/203143243): Add validation of build vs runtime values to ensure consistency.
JavaFile.builder(outputClassName.packageName(), classBuilder.build())
@@ -214,11 +232,8 @@ object SystemFeaturesGenerator {
features: Collection<FeatureInfo>,
) {
for (feature in features) {
- // Turn "FEATURE_FOO" into "hasFeatureFoo".
- val methodName =
- "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, feature.name)
val methodBuilder =
- MethodSpec.methodBuilder(methodName)
+ MethodSpec.methodBuilder(feature.methodName)
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addJavadoc("Check for ${feature.name}.\n\n@hide")
.returns(Boolean::class.java)
@@ -341,5 +356,32 @@ object SystemFeaturesGenerator {
builder.addMethod(methodBuilder.build())
}
- private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean)
+ /*
+ * Adds a metadata helper method that maps FEATURE_FOO names to their generated hasFeatureFoo()
+ * API counterpart, if defined.
+ */
+ private fun addMetadataMethodToClass(
+ builder: TypeSpec.Builder,
+ features: Collection<FeatureInfo>,
+ ) {
+ val methodBuilder =
+ MethodSpec.methodBuilder("getMethodNameForFeatureName")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addJavadoc("@return \"hasFeatureFoo\" if FEATURE_FOO is in the API, else null")
+ .returns(String::class.java)
+ .addParameter(String::class.java, "featureVarName")
+
+ methodBuilder.beginControlFlow("switch (featureVarName)")
+ for (feature in features) {
+ methodBuilder.addStatement("case \$S: return \$S", feature.name, feature.methodName)
+ }
+ methodBuilder.addStatement("default: return null").endControlFlow()
+
+ builder.addMethod(methodBuilder.build())
+ }
+
+ private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean) {
+ // Turn "FEATURE_FOO" into "hasFeatureFoo".
+ val methodName get() = "has" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name)
+ }
}
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
index 74ce6daaffc4..560454b65b7e 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java
@@ -36,8 +36,8 @@ public class SystemFeaturesMetadataProcessorTest {
@Test
public void testSdkFeatureCount() {
// See the fake PackageManager definition in this directory.
- // It defines 5 annotated features, and any/all other constants should be ignored.
- assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5);
+ // It defines 6 annotated features, and any/all other constants should be ignored.
+ assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(6);
}
@Test
diff --git a/tools/systemfeatures/tests/src/Context.java b/tools/systemfeatures/tests/src/android/content/Context.java
index 630bc0771a01..630bc0771a01 100644
--- a/tools/systemfeatures/tests/src/Context.java
+++ b/tools/systemfeatures/tests/src/android/content/Context.java
diff --git a/tools/systemfeatures/tests/src/FeatureInfo.java b/tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java
index 9d57edc64ca5..9d57edc64ca5 100644
--- a/tools/systemfeatures/tests/src/FeatureInfo.java
+++ b/tools/systemfeatures/tests/src/android/content/pm/FeatureInfo.java
diff --git a/tools/systemfeatures/tests/src/PackageManager.java b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java
index 839a9377476d..4a9edd61b55b 100644
--- a/tools/systemfeatures/tests/src/PackageManager.java
+++ b/tools/systemfeatures/tests/src/android/content/pm/PackageManager.java
@@ -36,6 +36,9 @@ public class PackageManager {
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_WIFI = "wifi";
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_NOT_IN_RO_FEATURE_API = "not_in_ro_feature_api";
+
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String FEATURE_INTENT_CATEGORY = "intent_category_with_feature_name_prefix";
@@ -47,4 +50,9 @@ public class PackageManager {
public boolean hasSystemFeature(String featureName, int version) {
return false;
}
+
+ /** @hide */
+ public boolean hasSystemFeature(String featureName) {
+ return hasSystemFeature(featureName, 0);
+ }
}
diff --git a/tools/systemfeatures/tests/src/ArrayMap.java b/tools/systemfeatures/tests/src/android/util/ArrayMap.java
index a5ed9b088896..a5ed9b088896 100644
--- a/tools/systemfeatures/tests/src/ArrayMap.java
+++ b/tools/systemfeatures/tests/src/android/util/ArrayMap.java
diff --git a/tools/systemfeatures/tests/src/ArraySet.java b/tools/systemfeatures/tests/src/android/util/ArraySet.java
index 0eb8f298bd89..0eb8f298bd89 100644
--- a/tools/systemfeatures/tests/src/ArraySet.java
+++ b/tools/systemfeatures/tests/src/android/util/ArraySet.java