summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/protolog/LegacyProtoLogImpl.java35
-rw-r--r--core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java80
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java409
-rw-r--r--core/java/com/android/internal/protolog/ProtoLog.java99
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogDataSource.java3
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java54
-rw-r--r--core/java/com/android/internal/protolog/common/IProtoLog.java18
-rw-r--r--core/java/com/android/internal/widget/OWNERS24
-rw-r--r--core/res/res/values-watch/colors.xml43
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt146
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt48
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt65
-rw-r--r--libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java5
-rw-r--r--libs/androidfw/StringPool.cpp26
-rw-r--r--libs/androidfw/include/androidfw/StringPool.h5
-rw-r--r--packages/PackageInstaller/Android.bp3
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml11
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java173
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java59
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java36
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java327
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java100
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java202
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java621
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java133
-rw-r--r--packages/SettingsLib/Android.bp1
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java326
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java136
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java137
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java160
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java113
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt72
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java62
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java62
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java165
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java41
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl19
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt74
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl23
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl27
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java232
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java126
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java167
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java94
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java163
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java98
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt55
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java170
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java169
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt84
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java79
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java152
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java125
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java17
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt17
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java9
-rw-r--r--packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt2
-rw-r--r--packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt64
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java69
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java100
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java2
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java21
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java127
-rw-r--r--services/core/java/com/android/server/wm/UnknownAppVisibilityController.java4
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java98
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java19
-rw-r--r--services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java5
-rw-r--r--tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java19
-rw-r--r--tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java173
-rw-r--r--tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java25
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp19
-rw-r--r--tools/aapt2/xml/XmlPullParser.h28
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt2
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt19
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt2
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt54
98 files changed, 4273 insertions, 2758 deletions
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 95b614666105..d24487412313 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -48,7 +48,6 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
@@ -66,7 +65,7 @@ public class LegacyProtoLogImpl implements IProtoLog {
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
- private final Map<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final Runnable mCacheUpdater;
private final int mPerChunkSize;
@@ -75,19 +74,20 @@ public class LegacyProtoLogImpl implements IProtoLog {
private final Object mProtoLogEnabledLock = new Object();
public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
- Runnable cacheUpdater) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, cacheUpdater);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups, cacheUpdater);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
- Runnable cacheUpdater) {
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
+ mLogGroups = logGroups;
mCacheUpdater = cacheUpdater;
}
@@ -97,25 +97,21 @@ public class LegacyProtoLogImpl implements IProtoLog {
@VisibleForTesting
@Override
public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args) {
+ @Nullable String messageString, Object[] args) {
if (group.isLogToProto()) {
logToProto(messageHash, paramsMask, args);
}
if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), level, messageHash, args);
+ logToLogcat(group.getTag(), level, messageHash, messageString, args);
}
}
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
- // This will be removed very soon so no point implementing it here.
- throw new IllegalStateException(
- "Not implemented. Only implemented for PerfettoProtoLogImpl.");
- }
-
- private void logToLogcat(String tag, LogLevel level, long messageHash, Object[] args) {
+ private void logToLogcat(String tag, LogLevel level, long messageHash,
+ @Nullable String messageString, Object[] args) {
String message = null;
- final String messageString = mViewerConfig.getViewerString(messageHash);
+ if (messageString == null) {
+ messageString = mViewerConfig.getViewerString(messageHash);
+ }
if (messageString != null) {
if (args != null) {
try {
@@ -414,12 +410,5 @@ public class LegacyProtoLogImpl implements IProtoLog {
// so we ignore the level argument to this function.
return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
}
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- for (IProtoLogGroup group : protoLogGroups) {
- mLogGroups.put(group.name(), group);
- }
- }
}
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
deleted file mode 100644
index 37d09c264c9b..000000000000
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.internal.protolog;
-
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.protolog.common.ILogger;
-import com.android.internal.protolog.common.IProtoLog;
-import com.android.internal.protolog.common.IProtoLogGroup;
-import com.android.internal.protolog.common.LogLevel;
-
-/**
- * Class only create and used to server temporarily for when there is source code pre-processing by
- * the ProtoLog tool, when the tracing to Perfetto flag is off, and the static REQUIRE_PROTOLOGTOOL
- * boolean is false. In which case we simply want to log protolog message to logcat. Note, that this
- * means that in such cases there is no real advantage of using protolog over logcat.
- *
- * @deprecated Should not be used. This is just a temporary class to support a legacy behavior.
- */
-@Deprecated
-public class LogcatOnlyProtoLogImpl implements IProtoLog {
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args) {
- throw new RuntimeException("Not supported when using LogcatOnlyProtoLogImpl");
- }
-
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object[] args) {
- String formattedString = TextUtils.formatSimple(messageString, args);
- switch (logLevel) {
- case VERBOSE -> Log.v(group.getTag(), formattedString);
- case INFO -> Log.i(group.getTag(), formattedString);
- case DEBUG -> Log.d(group.getTag(), formattedString);
- case WARN -> Log.w(group.getTag(), formattedString);
- case ERROR -> Log.e(group.getTag(), formattedString);
- case WTF -> Log.wtf(group.getTag(), formattedString);
- }
- }
-
- @Override
- public boolean isProtoEnabled() {
- return false;
- }
-
- @Override
- public int startLoggingToLogcat(String[] groups, ILogger logger) {
- return 0;
- }
-
- @Override
- public int stopLoggingToLogcat(String[] groups, ILogger logger) {
- return 0;
- }
-
- @Override
- public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- return true;
- }
-
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- // Does nothing
- }
-}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 5f6766e4926b..42fa6ac0407d 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -42,7 +42,6 @@ import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.SEQ_NEEDS_INCREMENTAL_STATE;
import static android.internal.perfetto.protos.TracePacketOuterClass.TracePacket.TIMESTAMP;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
import android.os.ShellCommand;
@@ -73,45 +72,37 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
-import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
/**
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
private static final String LOG_TAG = "ProtoLog";
- public static final String NULL_STRING = "null";
private final AtomicInteger mTracingInstances = new AtomicInteger();
private final ProtoLogDataSource mDataSource = new ProtoLogDataSource(
this::onTracingInstanceStart,
- this::onTracingFlush,
+ this::dumpTransitionTraceConfig,
this::onTracingInstanceStop
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final Runnable mCacheUpdater;
- private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
- private final Map<IProtoLogGroup, int[]> mLogLevelCounts = new ArrayMap<>();
- private final Map<IProtoLogGroup, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
+ private final Map<LogLevel, Integer> mDefaultLogLevelCounts = new ArrayMap<>();
+ private final Map<IProtoLogGroup, Map<LogLevel, Integer>> mLogLevelCounts = new ArrayMap<>();
- private final Lock mBackgroundServiceLock = new ReentrantLock();
- private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
+ private final ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(String viewerConfigFilePath, Runnable cacheUpdater) {
+ public PerfettoProtoLogImpl(String viewerConfigFilePath,
+ TreeMap<String, IProtoLogGroup> logGroups, Runnable cacheUpdater) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -119,19 +110,16 @@ public class PerfettoProtoLogImpl implements IProtoLog {
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- }, cacheUpdater);
- }
-
- public PerfettoProtoLogImpl() {
- this(null, null, () -> {});
+ }, logGroups, cacheUpdater);
}
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups,
cacheUpdater);
}
@@ -139,6 +127,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
ProtoLogViewerConfigReader viewerConfigReader,
+ TreeMap<String, IProtoLogGroup> logGroups,
Runnable cacheUpdater
) {
Producer.init(InitArguments.DEFAULTS);
@@ -151,6 +140,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
+ this.mLogGroups = logGroups;
this.mCacheUpdater = cacheUpdater;
}
@@ -159,70 +149,23 @@ public class PerfettoProtoLogImpl implements IProtoLog {
*/
@VisibleForTesting
@Override
- public void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- @Nullable Object[] args) {
- log(logLevel, group, new Message(messageHash, paramsMask), args);
- }
-
- @Override
- public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
- log(logLevel, group, new Message(messageString), args);
- }
-
- private void log(LogLevel logLevel, IProtoLogGroup group, Message message,
- @Nullable Object[] args) {
- if (isProtoEnabled()) {
- long tsNanos = SystemClock.elapsedRealtimeNanos();
- final String stacktrace;
- if (mCollectStackTraceGroupCounts.getOrDefault(group, 0) > 0) {
- stacktrace = collectStackTrace();
- } else {
- stacktrace = null;
- }
- try {
- mBackgroundServiceLock.lock();
- mBackgroundLoggingService.execute(() ->
- logToProto(logLevel, group, message, args, tsNanos,
- stacktrace));
- } finally {
- mBackgroundServiceLock.unlock();
- }
- }
- if (group.isLogToLogcat()) {
- logToLogcat(group.getTag(), logLevel, message, args);
- }
- }
-
- private void onTracingFlush() {
- final ExecutorService loggingService;
- try {
- mBackgroundServiceLock.lock();
- loggingService = mBackgroundLoggingService;
- mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- } finally {
- mBackgroundServiceLock.unlock();
- }
+ public void log(LogLevel level, IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString, Object[] args) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "log");
+ long tsNanos = SystemClock.elapsedRealtimeNanos();
try {
- loggingService.shutdown();
- boolean finished = loggingService.awaitTermination(10, TimeUnit.SECONDS);
-
- if (!finished) {
- Log.e(LOG_TAG, "ProtoLog background tracing service didn't finish gracefully.");
+ mBackgroundLoggingService.submit(() ->
+ logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
+ if (group.isLogToLogcat()) {
+ logToLogcat(group.getTag(), level, messageHash, messageString, args);
}
- } catch (InterruptedException e) {
- Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
-
- dumpTransitionTraceConfig();
}
private void dumpTransitionTraceConfig() {
- if (mViewerConfigInputStreamProvider == null) {
- // No viewer config available
- return;
- }
-
ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
if (pis == null) {
@@ -313,53 +256,39 @@ public class PerfettoProtoLogImpl implements IProtoLog {
os.end(outMessagesToken);
}
- private void logToLogcat(String tag, LogLevel level, Message message,
- @Nullable Object[] args) {
- String messageString = message.getMessage(mViewerConfigReader);
-
- if (messageString == null) {
- StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE");
- if (args != null) {
- builder.append(" args = (");
- builder.append(String.join(", ", Arrays.stream(args)
- .map(it -> {
- if (it == null) {
- return "null";
- } else {
- return it.toString();
- }
- }).toList()));
- builder.append(")");
- }
- messageString = builder.toString();
- args = new Object[0];
- }
-
- logToLogcat(tag, level, messageString, args);
- }
-
- private void logToLogcat(String tag, LogLevel level, String message, @Nullable Object[] args) {
+ private void logToLogcat(String tag, LogLevel level, long messageHash,
+ @Nullable String messageString, Object[] args) {
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToLogcat");
try {
- doLogToLogcat(tag, level, message, args);
+ doLogToLogcat(tag, level, messageHash, messageString, args);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void doLogToLogcat(String tag, LogLevel level, @NonNull String messageString,
- @Nullable Object[] args) {
- String message;
- if (args != null) {
- try {
- message = TextUtils.formatSimple(messageString, args);
- } catch (IllegalArgumentException e) {
- message = "FORMAT_ERROR \"" + messageString + "\", args=("
- + String.join(
- ", ", Arrays.stream(args).map(Object::toString).toList()) + ")";
+ private void doLogToLogcat(String tag, LogLevel level, long messageHash,
+ @androidx.annotation.Nullable String messageString, Object[] args) {
+ String message = null;
+ if (messageString == null) {
+ messageString = mViewerConfigReader.getViewerString(messageHash);
+ }
+ if (messageString != null) {
+ if (args != null) {
+ try {
+ message = TextUtils.formatSimple(messageString, args);
+ } catch (Exception ex) {
+ Slog.w(LOG_TAG, "Invalid ProtoLog format string.", ex);
+ }
+ } else {
+ message = messageString;
}
- } else {
- message = messageString;
+ }
+ if (message == null) {
+ StringBuilder builder = new StringBuilder("UNKNOWN MESSAGE (" + messageHash + ")");
+ for (Object o : args) {
+ builder.append(" ").append(o);
+ }
+ message = builder.toString();
}
passToLogcat(tag, level, message);
}
@@ -391,21 +320,25 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
}
- private void logToProto(LogLevel level, IProtoLogGroup logGroup, Message message, Object[] args,
- long tsNanos, @Nullable String stacktrace) {
+ private void logToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
+ Object[] args, long tsNanos) {
+ if (!isProtoEnabled()) {
+ return;
+ }
+
Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "logToProto");
try {
- doLogToProto(level, logGroup, message, args, tsNanos, stacktrace);
+ doLogToProto(level, groupName, messageHash, paramsMask, args, tsNanos);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
- private void doLogToProto(LogLevel level, IProtoLogGroup logGroup, Message message,
- Object[] args, long tsNanos, @Nullable String stacktrace) {
+ private void doLogToProto(LogLevel level, String groupName, long messageHash, int paramsMask,
+ Object[] args, long tsNanos) {
mDataSource.trace(ctx -> {
final ProtoLogDataSource.TlsState tlsState = ctx.getCustomTlsState();
- final LogLevel logFrom = tlsState.getLogFromLevel(logGroup.name());
+ final LogLevel logFrom = tlsState.getLogFromLevel(groupName);
if (level.ordinal() < logFrom.ordinal()) {
return;
@@ -417,44 +350,30 @@ public class PerfettoProtoLogImpl implements IProtoLog {
// trace processing easier.
int argIndex = 0;
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
+ int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
if (type == LogDataType.STRING) {
- if (o == null) {
- internStringArg(ctx, NULL_STRING);
- } else {
- internStringArg(ctx, o.toString());
- }
+ internStringArg(ctx, o.toString());
}
argIndex++;
}
}
int internedStacktrace = 0;
- if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
+ if (tlsState.getShouldCollectStacktrace(groupName)) {
// Intern stackstraces before creating the trace packet for the proto message so
// that the interned stacktrace strings appear before in the trace to make the
// trace processing easier.
+ String stacktrace = collectStackTrace();
internedStacktrace = internStacktraceString(ctx, stacktrace);
}
- boolean needsIncrementalState = false;
-
- long messageHash = 0;
- if (message.mMessageHash != null) {
- messageHash = message.mMessageHash;
- }
- if (message.mMessageString != null) {
- needsIncrementalState = true;
- messageHash =
- internProtoMessage(ctx, level, logGroup, message.mMessageString);
- }
-
final ProtoOutputStream os = ctx.newTracePacket();
os.write(TIMESTAMP, tsNanos);
long token = os.start(PROTOLOG_MESSAGE);
-
os.write(MESSAGE_ID, messageHash);
+ boolean needsIncrementalState = false;
+
if (args != null) {
int argIndex = 0;
@@ -462,39 +381,22 @@ public class PerfettoProtoLogImpl implements IProtoLog {
ArrayList<Double> doubleParams = new ArrayList<>();
ArrayList<Boolean> booleanParams = new ArrayList<>();
for (Object o : args) {
- int type = LogDataType.bitmaskToLogDataType(message.getMessageMask(), argIndex);
+ int type = LogDataType.bitmaskToLogDataType(paramsMask, argIndex);
try {
switch (type) {
case LogDataType.STRING:
- final int internedStringId;
- if (o == null) {
- internedStringId = internStringArg(ctx, NULL_STRING);
- } else {
- internedStringId = internStringArg(ctx, o.toString());
- }
+ final int internedStringId = internStringArg(ctx, o.toString());
os.write(STR_PARAM_IIDS, internedStringId);
needsIncrementalState = true;
break;
case LogDataType.LONG:
- if (o == null) {
- longParams.add(0);
- } else {
- longParams.add(((Number) o).longValue());
- }
+ longParams.add(((Number) o).longValue());
break;
case LogDataType.DOUBLE:
- if (o == null) {
- doubleParams.add(0d);
- } else {
- doubleParams.add(((Number) o).doubleValue());
- }
+ doubleParams.add(((Number) o).doubleValue());
break;
case LogDataType.BOOLEAN:
- if (o == null) {
- booleanParams.add(false);
- } else {
- booleanParams.add((boolean) o);
- }
+ booleanParams.add((boolean) o);
break;
}
} catch (ClassCastException ex) {
@@ -512,7 +414,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
booleanParams.forEach(it -> os.write(BOOLEAN_PARAMS, it ? 1 : 0));
}
- if (tlsState.getShouldCollectStacktrace(logGroup.name())) {
+ if (tlsState.getShouldCollectStacktrace(groupName)) {
os.write(STACKTRACE_IID, internedStacktrace);
}
@@ -525,63 +427,6 @@ public class PerfettoProtoLogImpl implements IProtoLog {
});
}
- private long internProtoMessage(
- TracingContext<ProtoLogDataSource.Instance, ProtoLogDataSource.TlsState,
- ProtoLogDataSource.IncrementalState> ctx, LogLevel level,
- IProtoLogGroup logGroup, String message) {
- final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
-
- if (!incrementalState.clearReported) {
- final ProtoOutputStream os = ctx.newTracePacket();
- os.write(SEQUENCE_FLAGS, SEQ_INCREMENTAL_STATE_CLEARED);
- incrementalState.clearReported = true;
- }
-
-
- if (!incrementalState.protologGroupInterningSet.contains(logGroup.getId())) {
- incrementalState.protologGroupInterningSet.add(logGroup.getId());
-
- final ProtoOutputStream os = ctx.newTracePacket();
- final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
- final long groupConfigToken = os.start(GROUPS);
-
- os.write(ID, logGroup.getId());
- os.write(NAME, logGroup.name());
- os.write(TAG, logGroup.getTag());
-
- os.end(groupConfigToken);
- os.end(protologViewerConfigToken);
- }
-
- final Long messageHash = hash(level, logGroup.name(), message);
- if (!incrementalState.protologMessageInterningSet.contains(messageHash)) {
- incrementalState.protologMessageInterningSet.add(messageHash);
-
- final ProtoOutputStream os = ctx.newTracePacket();
- final long protologViewerConfigToken = os.start(PROTOLOG_VIEWER_CONFIG);
- final long messageConfigToken = os.start(MESSAGES);
-
- os.write(MessageData.MESSAGE_ID, messageHash);
- os.write(MESSAGE, message);
- os.write(LEVEL, level.ordinal());
- os.write(GROUP_ID, logGroup.getId());
-
- os.end(messageConfigToken);
- os.end(protologViewerConfigToken);
- }
-
- return messageHash;
- }
-
- private Long hash(
- LogLevel logLevel,
- String logGroup,
- String messageString
- ) {
- final String fullStringIdentifier = messageString + logLevel + logGroup;
- return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
- }
-
private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
private String collectStackTrace() {
@@ -621,7 +466,7 @@ public class PerfettoProtoLogImpl implements IProtoLog {
ProtoLogDataSource.IncrementalState> ctx,
Map<String, Integer> internMap,
long fieldId,
- @NonNull String string
+ String string
) {
final ProtoLogDataSource.IncrementalState incrementalState = ctx.getIncrementalState();
@@ -678,17 +523,25 @@ public class PerfettoProtoLogImpl implements IProtoLog {
@Override
public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- final int[] groupLevelCount = mLogLevelCounts.get(group);
- return (groupLevelCount == null && mDefaultLogLevelCounts[level.ordinal()] > 0)
- || (groupLevelCount != null && groupLevelCount[level.ordinal()] > 0)
- || group.isLogToLogcat();
+ return group.isLogToLogcat() || getLogFromLevel(group).ordinal() <= level.ordinal();
}
- @Override
- public void registerGroups(IProtoLogGroup... protoLogGroups) {
- for (IProtoLogGroup protoLogGroup : protoLogGroups) {
- mLogGroups.put(protoLogGroup.name(), protoLogGroup);
+ private LogLevel getLogFromLevel(IProtoLogGroup group) {
+ if (mLogLevelCounts.containsKey(group)) {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mLogLevelCounts.get(group).getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
+ } else {
+ for (LogLevel logLevel : LogLevel.values()) {
+ if (mDefaultLogLevelCounts.getOrDefault(logLevel, 0) > 0) {
+ return logLevel;
+ }
+ }
}
+
+ return LogLevel.WTF;
}
/**
@@ -767,51 +620,36 @@ public class PerfettoProtoLogImpl implements IProtoLog {
}
private synchronized void onTracingInstanceStart(ProtoLogDataSource.ProtoLogConfig config) {
+ this.mTracingInstances.incrementAndGet();
+
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- mDefaultLogLevelCounts[i]++;
- }
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.getOrDefault(defaultLogFrom, 0) + 1);
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- if (group == null) {
- throw new IllegalArgumentException("Trying to set config for \""
- + overriddenGroupTag + "\" that isn't registered");
- }
-
- mLogLevelCounts.putIfAbsent(group, new int[LogLevel.values().length]);
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- for (int i = logFromLevel.ordinal(); i < LogLevel.values().length; i++) {
- logLevelsCountsForGroup[i]++;
- }
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
- }
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.getOrDefault(group, 0) + 1);
- }
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.getOrDefault(logFromLevel, 0) + 1);
}
mCacheUpdater.run();
-
- this.mTracingInstances.incrementAndGet();
}
private synchronized void onTracingInstanceStop(ProtoLogDataSource.ProtoLogConfig config) {
this.mTracingInstances.decrementAndGet();
final LogLevel defaultLogFrom = config.getDefaultGroupConfig().logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- mDefaultLogLevelCounts[i]--;
+ mDefaultLogLevelCounts.put(defaultLogFrom,
+ mDefaultLogLevelCounts.get(defaultLogFrom) - 1);
+ if (mDefaultLogLevelCounts.get(defaultLogFrom) <= 0) {
+ mDefaultLogLevelCounts.remove(defaultLogFrom);
}
final Set<String> overriddenGroupTags = config.getGroupTagsWithOverriddenConfigs();
@@ -819,24 +657,18 @@ public class PerfettoProtoLogImpl implements IProtoLog {
for (String overriddenGroupTag : overriddenGroupTags) {
IProtoLogGroup group = mLogGroups.get(overriddenGroupTag);
- final int[] logLevelsCountsForGroup = mLogLevelCounts.get(group);
+ mLogLevelCounts.putIfAbsent(group, new ArrayMap<>());
+ final Map<LogLevel, Integer> logLevelsCountsForGroup = mLogLevelCounts.get(group);
final LogLevel logFromLevel = config.getConfigFor(overriddenGroupTag).logFrom;
- for (int i = defaultLogFrom.ordinal(); i < LogLevel.values().length; i++) {
- logLevelsCountsForGroup[i]--;
+ logLevelsCountsForGroup.put(logFromLevel,
+ logLevelsCountsForGroup.get(logFromLevel) - 1);
+ if (logLevelsCountsForGroup.get(logFromLevel) <= 0) {
+ logLevelsCountsForGroup.remove(logFromLevel);
}
- if (Arrays.stream(logLevelsCountsForGroup).allMatch(it -> it == 0)) {
+ if (logLevelsCountsForGroup.isEmpty()) {
mLogLevelCounts.remove(group);
}
-
- if (config.getConfigFor(overriddenGroupTag).collectStackTrace) {
- mCollectStackTraceGroupCounts.put(group,
- mCollectStackTraceGroupCounts.get(group) - 1);
-
- if (mCollectStackTraceGroupCounts.get(group) == 0) {
- mCollectStackTraceGroupCounts.remove(group);
- }
- }
}
mCacheUpdater.run();
@@ -849,36 +681,5 @@ public class PerfettoProtoLogImpl implements IProtoLog {
pw.flush();
}
}
-
- private static class Message {
- private final Long mMessageHash;
- private final Integer mMessageMask;
- private final String mMessageString;
-
- private Message(Long messageHash, int messageMask) {
- this.mMessageHash = messageHash;
- this.mMessageMask = messageMask;
- this.mMessageString = null;
- }
-
- private Message(String messageString) {
- this.mMessageHash = null;
- final List<Integer> argTypes = LogDataType.parseFormatString(messageString);
- this.mMessageMask = LogDataType.logDataTypesToBitMask(argTypes);
- this.mMessageString = messageString;
- }
-
- private int getMessageMask() {
- return mMessageMask;
- }
-
- private String getMessage(ProtoLogViewerConfigReader viewerConfigReader) {
- if (mMessageString != null) {
- return mMessageString;
- }
-
- return viewerConfigReader.getViewerString(mMessageHash);
- }
- }
}
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 99d441812fa8..0118c056d682 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -44,23 +44,21 @@ public class ProtoLog {
// LINT.ThenChange(frameworks/base/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt)
// Needs to be set directly otherwise the protologtool tries to transform the method call
- @Deprecated
public static boolean REQUIRE_PROTOLOGTOOL = true;
- private static IProtoLog sProtoLogInstance;
-
/**
* DEBUG level log.
*
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void d(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.DEBUG, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -69,12 +67,13 @@ public class ProtoLog {
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void v(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.VERBOSE, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -83,12 +82,13 @@ public class ProtoLog {
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void i(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.INFO, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -97,12 +97,13 @@ public class ProtoLog {
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void w(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.WARN, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -111,12 +112,13 @@ public class ProtoLog {
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void e(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.ERROR, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -125,12 +127,13 @@ public class ProtoLog {
* @param group {@code IProtoLogGroup} controlling this log call.
* @param messageString constant format string for the logged message.
* @param args parameters to be used with the format string.
- *
- * NOTE: If source code is pre-processed by ProtoLogTool this is not the function call that is
- * executed. Check generated code for actual call.
*/
public static void wtf(IProtoLogGroup group, String messageString, Object... args) {
- logStringMessage(LogLevel.WTF, group, messageString, args);
+ // Stub, replaced by the ProtoLogTool.
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
}
/**
@@ -139,7 +142,11 @@ public class ProtoLog {
* @return true iff this is being logged.
*/
public static boolean isEnabled(IProtoLogGroup group, LogLevel level) {
- return sProtoLogInstance.isEnabled(group, level);
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
+ }
+ return false;
}
/**
@@ -147,38 +154,10 @@ public class ProtoLog {
* @return A singleton instance of ProtoLog.
*/
public static IProtoLog getSingleInstance() {
- return sProtoLogInstance;
- }
-
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- sProtoLogInstance.registerGroups(protoLogGroups);
- }
-
- private static void logStringMessage(LogLevel logLevel, IProtoLogGroup group,
- String stringMessage, Object... args) {
- if (sProtoLogInstance == null) {
- throw new IllegalStateException(
- "Trying to use ProtoLog before it is initialized in this process.");
- }
-
- if (sProtoLogInstance.isEnabled(group, logLevel)) {
- sProtoLogInstance.log(logLevel, group, stringMessage, args);
- }
- }
-
- static {
- if (android.tracing.Flags.perfettoProtologTracing()) {
- sProtoLogInstance = new PerfettoProtoLogImpl();
- } else {
- if (REQUIRE_PROTOLOGTOOL) {
- throw new RuntimeException("REQUIRE_PROTOLOGTOOL not set to false.");
- } else {
- sProtoLogInstance = new LogcatOnlyProtoLogImpl();
- }
+ if (REQUIRE_PROTOLOGTOOL) {
+ throw new UnsupportedOperationException(
+ "ProtoLog calls MUST be processed with ProtoLogTool");
}
+ return null;
}
}
diff --git a/core/java/com/android/internal/protolog/ProtoLogDataSource.java b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
index 84f3237142b8..6ab79b92784e 100644
--- a/core/java/com/android/internal/protolog/ProtoLogDataSource.java
+++ b/core/java/com/android/internal/protolog/ProtoLogDataSource.java
@@ -40,7 +40,6 @@ import com.android.internal.protolog.common.LogLevel;
import java.io.IOException;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -139,8 +138,6 @@ public class ProtoLogDataSource extends DataSource<ProtoLogDataSource.Instance,
}
public static class IncrementalState {
- public final Set<Integer> protologGroupInterningSet = new HashSet<>();
- public final Set<Long> protologMessageInterningSet = new HashSet<>();
public final Map<String, Integer> argumentInterningMap = new HashMap<>();
public final Map<String, Integer> stacktraceInterningMap = new HashMap<>();
public boolean clearReported = false;
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 3082295a522c..6d142afce626 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -54,33 +54,48 @@ public class ProtoLogImpl {
private static Runnable sCacheUpdater;
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void d(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.DEBUG, group, messageHash, paramsMask, args);
+ public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.DEBUG, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void v(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, args);
+ public static void v(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.VERBOSE, group, messageHash, paramsMask, messageString,
+ args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void i(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, args);
+ public static void i(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.INFO, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void w(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, args);
+ public static void w(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WARN, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void e(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.ERROR, group, messageHash, paramsMask, args);
+ public static void e(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance()
+ .log(LogLevel.ERROR, group, messageHash, paramsMask, messageString, args);
}
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
- public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask, Object... args) {
- getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, args);
+ public static void wtf(IProtoLogGroup group, long messageHash, int paramsMask,
+ @Nullable String messageString,
+ Object... args) {
+ getSingleInstance().log(LogLevel.WTF, group, messageHash, paramsMask, messageString, args);
}
/**
@@ -92,27 +107,18 @@ public class ProtoLogImpl {
}
/**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- public static void registerGroups(IProtoLogGroup... protoLogGroups) {
- getSingleInstance().registerGroups(protoLogGroups);
- }
-
- /**
* Returns the single instance of the ProtoLogImpl singleton class.
*/
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+ sServiceInstance = new PerfettoProtoLogImpl(
+ sViewerConfigPath, sLogGroups, sCacheUpdater);
} else {
sServiceInstance = new LegacyProtoLogImpl(
- sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups, sCacheUpdater);
}
- IProtoLogGroup[] groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
- sServiceInstance.registerGroups(groups);
sCacheUpdater.run();
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index f5695acd0614..f72d9f79958d 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -27,19 +27,11 @@ public interface IProtoLog {
* @param group The group this message belongs to.
* @param messageHash The hash of the message.
* @param paramsMask The parameters mask of the message.
- * @param args The arguments of the message.
- */
- void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
- Object[] args);
-
- /**
- * Log a ProtoLog message
- * @param logLevel Log level of the proto message.
- * @param group The group this message belongs to.
* @param messageString The message string.
* @param args The arguments of the message.
*/
- void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args);
+ void log(LogLevel logLevel, IProtoLogGroup group, long messageHash, int paramsMask,
+ String messageString, Object[] args);
/**
* Check if ProtoLog is tracing.
@@ -68,10 +60,4 @@ public interface IProtoLog {
* @return If we need to log this group and level to either ProtoLog or Logcat.
*/
boolean isEnabled(IProtoLogGroup group, LogLevel level);
-
- /**
- * Registers available protolog groups. A group must be registered before it can be used.
- * @param protoLogGroups The groups to register for use in protolog.
- */
- void registerGroups(IProtoLogGroup... protoLogGroups);
}
diff --git a/core/java/com/android/internal/widget/OWNERS b/core/java/com/android/internal/widget/OWNERS
index e2672f5b03ba..cf2f202a03ac 100644
--- a/core/java/com/android/internal/widget/OWNERS
+++ b/core/java/com/android/internal/widget/OWNERS
@@ -9,18 +9,18 @@ per-file *Lockscreen* = file:/services/core/java/com/android/server/locksettings
per-file *LockSettings* = file:/services/core/java/com/android/server/locksettings/OWNERS
# Notification related
-per-file *Notification* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *Messaging* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *Message* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *Conversation* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *People* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file *ImageResolver* = file:/services/core/java/com/android/server/notification/OWNERS
-per-file CallLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
-per-file CachingIconView.java = file:/services/core/java/com/android/server/notification/OWNERS
-per-file ImageFloatingTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
-per-file ObservableTextView.java = file:/services/core/java/com/android/server/notification/OWNERS
-per-file RemeasuringLinearLayout.java = file:/services/core/java/com/android/server/notification/OWNERS
-per-file ViewClippingUtil.java = file:/services/core/java/com/android/server/notification/OWNERS
+per-file *Notification* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file *Messaging* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file *Message* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file *Conversation* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file *People* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file *ImageResolver* = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file CallLayout.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file CachingIconView.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file ImageFloatingTextView.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file ObservableTextView.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file RemeasuringLinearLayout.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
+per-file ViewClippingUtil.java = file:/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
# Appwidget related
per-file *RemoteViews* = file:/services/appwidget/java/com/android/server/appwidget/OWNERS
diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml
deleted file mode 100644
index e2b7505f3c6e..000000000000
--- a/core/res/res/values-watch/colors.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<!-- Watch specific system colors. -->
-<resources>
- <color name="system_error_light">#B3261E</color>
- <color name="system_on_error_light">#FFFFFF</color>
- <color name="system_error_container_light">#F7DCDA</color>
- <color name="system_on_error_container_light">#410E0B</color>
-
- <color name="system_error_dark">#F2B8B5</color>
- <color name="system_on_error_dark">#601410</color>
- <color name="system_error_container_dark">#FF8986</color>
- <color name="system_on_error_container_dark">#410E0B</color>
-
- <!-- With material deprecation of 'background' in favor of 'surface' we flatten these
- on watches to match the black background requirements -->
- <color name="system_surface_dark">#000000</color>
- <color name="system_surface_dim_dark">#000000</color>
- <color name="system_surface_bright_dark">#000000</color>
-
- <!-- Wear flattens the typical 5 container layers to 3; container + high & low -->
- <color name="system_surface_container_dark">#303030</color>
- <color name="system_surface_variant_dark">#303030</color>
- <color name="system_surface_container_high_dark">#474747</color>
- <color name="system_surface_container_highest_dark">#474747</color>
- <color name="system_surface_container_low_dark">#252626</color>
- <color name="system_surface_container_lowest_dark">#252626</color>
-
-</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 66e1d7af6311..aa499d9ee8a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -585,9 +585,10 @@ public abstract class WMShellModule {
@Provides
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context
- ) {
- return new ExitDesktopTaskTransitionHandler(transitions, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
+ return new ExitDesktopTaskTransitionHandler(
+ transitions, context, interactionJankMonitor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 891f75cfdbda..171378f9a164 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -42,6 +42,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.jank.Cuj;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
@@ -60,6 +62,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
private final Context mContext;
private final Transitions mTransitions;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
@@ -67,17 +70,21 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
public ExitDesktopTaskTransitionHandler(
Transitions transitions,
- Context context) {
- this(transitions, SurfaceControl.Transaction::new, context);
+ Context context,
+ InteractionJankMonitor interactionJankMonitor
+ ) {
+ this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor);
}
private ExitDesktopTaskTransitionHandler(
Transitions transitions,
Supplier<SurfaceControl.Transaction> supplier,
- Context context) {
+ Context context,
+ InteractionJankMonitor interactionJankMonitor) {
mTransitions = transitions;
mTransactionSupplier = supplier;
mContext = context;
+ mInteractionJankMonitor = interactionJankMonitor;
}
/**
@@ -146,6 +153,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
final int screenHeight = metrics.heightPixels;
final SurfaceControl sc = change.getLeash();
final Rect endBounds = change.getEndAbsBounds();
+ mInteractionJankMonitor
+ .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
// Hide the first (fullscreen) frame because the animation will start from the freeform
// size.
startT.hide(sc)
@@ -175,6 +184,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
if (mOnAnimationFinishedCallback != null) {
mOnAnimationFinishedCallback.accept(finishT);
}
+ mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
() -> finishCallback.onTransitionFinished(null));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index dd4595a70211..3a680097554f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -27,7 +27,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
@@ -76,7 +75,6 @@ public class ShellInit {
*/
@VisibleForTesting
public void init() {
- ProtoLog.registerGroups(ShellProtoLogGroup.values());
ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
SurfaceControl.setDebugUsageAfterRelease(true);
// Init in order of registration
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
index 32217d87b130..c725b08d2f5e 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt
@@ -21,11 +21,10 @@ import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd
+import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
-import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd
import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart
-import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds
import android.tools.flicker.assertors.assertions.LauncherWindowReplacesAppAsTopWindow
import android.tools.flicker.config.AssertionTemplates
import android.tools.flicker.config.FlickerConfigEntry
@@ -46,28 +45,27 @@ class DesktopModeFlickerScenarios {
FlickerConfigEntry(
scenarioId = ScenarioId("END_DRAG_TO_DESKTOP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- return transitions.filter {
- it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
- }
- }
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ return transitions.filter {
+ it.type == TransitionType.DESKTOP_MODE_END_DRAG_TO_DESKTOP
}
- ),
+ }
+ }
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowHasDesktopModeInitialBoundsAtTheEnd(
- Components.DESKTOP_MODE_APP
- )
+ AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP),
+ AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
+ AppWindowHasDesktopModeInitialBoundsAtTheEnd(
+ Components.DESKTOP_MODE_APP
)
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
// Use this scenario for closing an app in desktop windowing, except the last app. For the
@@ -76,77 +74,85 @@ class DesktopModeFlickerScenarios {
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_APP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- // In case there are multiple windows closing, filter out the
- // last window closing. It should use the CLOSE_LAST_APP
- // scenario below.
- return transitions
- .filter { it.type == TransitionType.CLOSE }
- .sortedByDescending { it.id }
- .drop(1)
- }
- }
- ),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ // In case there are multiple windows closing, filter out the
+ // last window closing. It should use the CLOSE_LAST_APP
+ // scenario below.
+ return transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .sortedByDescending { it.id }
+ .drop(1)
+ }
+ }
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
- AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- )
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowOnTopAtStart(Components.DESKTOP_MODE_APP),
+ AppLayerIsVisibleAtStart(Components.DESKTOP_MODE_APP),
+ AppLayerIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CLOSE_LAST_APP =
FlickerConfigEntry(
scenarioId = ScenarioId("CLOSE_LAST_APP"),
extractor =
- ShellTransitionScenarioExtractor(
- transitionMatcher =
- object : ITransitionMatcher {
- override fun findAll(
- transitions: Collection<Transition>
- ): Collection<Transition> {
- val lastTransition =
- transitions
- .filter { it.type == TransitionType.CLOSE }
- .maxByOrNull { it.id }!!
- return listOf(lastTransition)
- }
- }
- ),
+ ShellTransitionScenarioExtractor(
+ transitionMatcher =
+ object : ITransitionMatcher {
+ override fun findAll(
+ transitions: Collection<Transition>
+ ): Collection<Transition> {
+ val lastTransition =
+ transitions
+ .filter { it.type == TransitionType.CLOSE }
+ .maxByOrNull { it.id }!!
+ return listOf(lastTransition)
+ }
+ }
+ ),
assertions =
- AssertionTemplates.COMMON_ASSERTIONS +
+ AssertionTemplates.COMMON_ASSERTIONS +
listOf(
- AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
- LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
- AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
- )
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AppWindowIsInvisibleAtEnd(Components.DESKTOP_MODE_APP),
+ LauncherWindowReplacesAppAsTopWindow(Components.DESKTOP_MODE_APP),
+ AppWindowIsInvisibleAtEnd(DESKTOP_WALLPAPER)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
val CORNER_RESIZE =
FlickerConfigEntry(
scenarioId = ScenarioId("CORNER_RESIZE"),
extractor =
- TaggedScenarioExtractorBuilder()
+ TaggedScenarioExtractorBuilder()
.setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
.setTransitionMatcher(
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
+ )
+
+ val CORNER_RESIZE_TO_MINIMUM_SIZE =
+ FlickerConfigEntry(
+ scenarioId = ScenarioId("CORNER_RESIZE_TO_MINIMUM_SIZE"),
+ extractor =
+ TaggedScenarioExtractorBuilder()
+ .setTargetTag(CujType.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ .setTransitionMatcher(
+ TaggedCujTransitionMatcher(associatedTransitionRequired = false)
+ ).build(),
assertions =
- listOf(
- AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP),
- AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP),
- AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP),
- )
- .associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
+ AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(
+ AppWindowHasSizeOfAtLeast(Components.DESKTOP_MODE_APP, 770, 700)
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
new file mode 100644
index 000000000000..6319cf74ed8f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizeLandscape.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.Rotation
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in
+ * landscape mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizeLandscape : ResizeAppWithCornerResize(
+ rotation = Rotation.ROTATION_90,
+ horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
new file mode 100644
index 000000000000..431f6e3d3ea2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/ResizeAppToMinimumWindowSizePortrait.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.wm.shell.flicker.service.desktopmode.flicker
+
+import android.tools.flicker.FlickerConfig
+import android.tools.flicker.annotation.ExpectedScenarios
+import android.tools.flicker.annotation.FlickerConfigProvider
+import android.tools.flicker.config.FlickerConfig
+import android.tools.flicker.config.FlickerServiceConfig
+import android.tools.flicker.junit.FlickerServiceJUnit4ClassRunner
+import com.android.wm.shell.flicker.service.desktopmode.flicker.DesktopModeFlickerScenarios.Companion.CORNER_RESIZE_TO_MINIMUM_SIZE
+import com.android.wm.shell.flicker.service.desktopmode.scenarios.ResizeAppWithCornerResize
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Resize app window using corner resize to the smallest possible height and width in portrait mode.
+ *
+ * Assert that the minimum window size constraint is maintained.
+ */
+@RunWith(FlickerServiceJUnit4ClassRunner::class)
+class ResizeAppToMinimumWindowSizePortrait : ResizeAppWithCornerResize(horizontalChange = -1500,
+ verticalChange = 1500) {
+ @ExpectedScenarios(["CORNER_RESIZE_TO_MINIMUM_SIZE"])
+ @Test
+ override fun resizeAppWithCornerResize() = super.resizeAppWithCornerResize()
+
+ companion object {
+ @JvmStatic
+ @FlickerConfigProvider
+ fun flickerConfigProvider(): FlickerConfig =
+ FlickerConfig().use(FlickerServiceConfig.DEFAULT).use(CORNER_RESIZE_TO_MINIMUM_SIZE)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
new file mode 100644
index 000000000000..20e2167c28f2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/MaximizeAppWindow.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.wm.shell.flicker.service.desktopmode.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.flicker.service.common.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/** Base scenario test for maximize app window CUJ in desktop mode. */
+@Ignore("Base Test Class")
+abstract class MaximizeAppWindow
+{
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL,
+ Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ testApp.enterDesktopWithDrag(wmHelper, device)
+ }
+
+ @Test
+ open fun maximizeAppWindow() {
+ testApp.maximiseDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
index ac9089a5c1bd..136cf378aa09 100644
--- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt
@@ -38,7 +38,9 @@ import org.junit.Test
@Ignore("Base Test Class")
abstract class ResizeAppWithCornerResize
@JvmOverloads
-constructor(val rotation: Rotation = Rotation.ROTATION_0) {
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+ val horizontalChange: Int = 50,
+ val verticalChange: Int = -50) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
private val tapl = LauncherInstrumentation()
@@ -46,7 +48,9 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
private val device = UiDevice.getInstance(instrumentation)
private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
- @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+ @Rule
+ @JvmField
+ val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
@Before
fun setup() {
@@ -58,7 +62,11 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) {
@Test
open fun resizeAppWithCornerResize() {
- testApp.cornerResize(wmHelper, device, DesktopModeAppHelper.Corners.RIGHT_TOP, 50, -50)
+ testApp.cornerResize(wmHelper,
+ device,
+ DesktopModeAppHelper.Corners.RIGHT_TOP,
+ horizontalChange,
+ verticalChange)
}
@After
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index b2467e9a62cf..e5157c974e2d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -45,6 +45,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
@@ -65,6 +66,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
@Mock
private Transitions mTransitions;
@Mock
+ private InteractionJankMonitor mInteractionJankMonitor;
+ @Mock
IBinder mToken;
@Mock
Supplier<SurfaceControl.Transaction> mTransactionFactory;
@@ -94,7 +97,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
.thenReturn(getContext().getResources().getDisplayMetrics());
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
- mContext);
+ mContext, mInteractionJankMonitor);
mPoint = new Point(0, 0);
}
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index ad445c042e63..629f14683b19 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -297,24 +297,22 @@ void StringPool::Prune() {
template <typename E>
static void SortEntries(
std::vector<std::unique_ptr<E>>& entries,
- const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) {
+ base::function_ref<int(const StringPool::Context&, const StringPool::Context&)> cmp) {
using UEntry = std::unique_ptr<E>;
+ std::sort(entries.begin(), entries.end(), [cmp](const UEntry& a, const UEntry& b) -> bool {
+ int r = cmp(a->context, b->context);
+ if (r == 0) {
+ r = a->value.compare(b->value);
+ }
+ return r < 0;
+ });
+}
- if (cmp != nullptr) {
- std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool {
- int r = cmp(a->context, b->context);
- if (r == 0) {
- r = a->value.compare(b->value);
- }
- return r < 0;
- });
- } else {
- std::sort(entries.begin(), entries.end(),
- [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; });
- }
+void StringPool::Sort() {
+ Sort([](auto&&, auto&&) { return 0; });
}
-void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) {
+void StringPool::Sort(base::function_ref<int(const Context&, const Context&)> cmp) {
SortEntries(styles_, cmp);
SortEntries(strings_, cmp);
ReAssignIndices();
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 0190ab57bf23..9b2c72a29f48 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -17,7 +17,6 @@
#ifndef _ANDROID_STRING_POOL_H
#define _ANDROID_STRING_POOL_H
-#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
@@ -25,6 +24,7 @@
#include "BigBuffer.h"
#include "IDiagnostics.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include "androidfw/ConfigDescription.h"
#include "androidfw/StringPiece.h"
@@ -205,7 +205,8 @@ class StringPool {
// Sorts the strings according to their Context using some comparison function.
// Equal Contexts are further sorted by string value, lexicographically.
// If no comparison function is provided, values are only sorted lexicographically.
- void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr);
+ void Sort();
+ void Sort(base::function_ref<int(const Context&, const Context&)> cmp);
// Removes any strings that have no references.
void Prune();
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 79c810ca2611..bd84b58aa0f4 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -46,7 +46,6 @@ android_app {
sdk_version: "system_current",
rename_resources_package: false,
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
@@ -79,7 +78,6 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.fragment_fragment",
"androidx.lifecycle_lifecycle-livedata",
@@ -112,7 +110,6 @@ android_app {
overrides: ["PackageInstaller"],
static_libs: [
- "xz-java",
"androidx.leanback_leanback",
"androidx.annotation_annotation",
"androidx.fragment_fragment",
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 443747530315..68443a7e1492 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -147,17 +147,6 @@
android:configChanges="mnc|mnc|touchscreen|navigation|screenLayout|screenSize|smallestScreenSize|orientation|locale|keyboard|keyboardHidden|fontScale|uiMode|layoutDirection|density"
android:exported="false" />
- <!-- Wearable Components -->
- <service android:name=".wear.WearPackageInstallerService"
- android:permission="com.google.android.permission.INSTALL_WEARABLE_PACKAGES"
- android:foregroundServiceType="systemExempted"
- android:exported="true"/>
-
- <provider android:name=".wear.WearPackageIconProvider"
- android:authorities="com.google.android.packageinstaller.wear.provider"
- android:grantUriPermissions="true"
- android:exported="false" />
-
<receiver android:name="androidx.profileinstaller.ProfileInstallReceiver"
tools:node="remove" />
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
deleted file mode 100644
index 53a460dc18ca..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallTask.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Context;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Task that installs an APK. This must not be called on the main thread.
- * This code is based off the Finsky/Wearsky implementation
- */
-public class InstallTask {
- private static final String TAG = "InstallTask";
-
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
- private final Context mContext;
- private String mPackageName;
- private ParcelFileDescriptor mParcelFileDescriptor;
- private PackageInstallerImpl.InstallListener mCallback;
- private PackageInstaller.Session mSession;
- private IntentSender mCommitCallback;
-
- private Exception mException = null;
- private int mErrorCode = 0;
- private String mErrorDesc = null;
-
- public InstallTask(Context context, String packageName,
- ParcelFileDescriptor parcelFileDescriptor,
- PackageInstallerImpl.InstallListener callback, PackageInstaller.Session session,
- IntentSender commitCallback) {
- mContext = context;
- mPackageName = packageName;
- mParcelFileDescriptor = parcelFileDescriptor;
- mCallback = callback;
- mSession = session;
- mCommitCallback = commitCallback;
- }
-
- public boolean isError() {
- return mErrorCode != InstallerConstants.STATUS_SUCCESS || !TextUtils.isEmpty(mErrorDesc);
- }
-
- public void execute() {
- if (Looper.myLooper() == Looper.getMainLooper()) {
- throw new IllegalStateException("This method cannot be called from the UI thread.");
- }
-
- OutputStream sessionStream = null;
- try {
- sessionStream = mSession.openWrite(mPackageName, 0, -1);
-
- // 2b: Stream the asset to the installer. Note:
- // Note: writeToOutputStreamFromAsset() always safely closes the input stream
- writeToOutputStreamFromAsset(sessionStream);
- mSession.fsync(sessionStream);
- } catch (Exception e) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM;
- mErrorDesc = "Could not write to stream";
- } finally {
- if (sessionStream != null) {
- // 2c: close output stream
- try {
- sessionStream.close();
- } catch (Exception e) {
- // Ignore otherwise
- if (mException == null) {
- mException = e;
- mErrorCode = InstallerConstants.ERROR_INSTALL_CLOSE_STREAM;
- mErrorDesc = "Could not close session stream";
- }
- }
- }
- }
-
- if (mErrorCode != InstallerConstants.STATUS_SUCCESS) {
- // An error occurred, we're done
- Log.e(TAG, "Exception while installing " + mPackageName + ": " + mErrorCode + ", "
- + mErrorDesc + ", " + mException);
- mSession.close();
- mCallback.installFailed(mErrorCode, "[" + mPackageName + "]" + mErrorDesc);
- } else {
- // 3. Commit the session (this actually installs it.) Session map
- // will be cleaned up in the callback.
- mCallback.installBeginning();
- mSession.commit(mCommitCallback);
- mSession.close();
- }
- }
-
- /**
- * {@code PackageInstaller} works with streams. Get the {@code FileDescriptor}
- * corresponding to the {@code Asset} and then write the contents into an
- * {@code OutputStream} that is passed in.
- * <br>
- * The {@code FileDescriptor} is closed but the {@code OutputStream} is not closed.
- */
- private boolean writeToOutputStreamFromAsset(OutputStream outputStream) {
- if (outputStream == null) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_COPY_STREAM_EXCEPTION;
- mErrorDesc = "Got a null OutputStream.";
- return false;
- }
-
- if (mParcelFileDescriptor == null || mParcelFileDescriptor.getFileDescriptor() == null) {
- mErrorCode = InstallerConstants.ERROR_COULD_NOT_GET_FD;
- mErrorDesc = "Could not get FD";
- return false;
- }
-
- InputStream inputStream = null;
- try {
- byte[] inputBuf = new byte[DEFAULT_BUFFER_SIZE];
- int bytesRead;
- inputStream = new ParcelFileDescriptor.AutoCloseInputStream(mParcelFileDescriptor);
-
- while ((bytesRead = inputStream.read(inputBuf)) > -1) {
- if (bytesRead > 0) {
- outputStream.write(inputBuf, 0, bytesRead);
- }
- }
-
- outputStream.flush();
- } catch (IOException e) {
- mErrorCode = InstallerConstants.ERROR_INSTALL_APK_COPY_FAILURE;
- mErrorDesc = "Reading from Asset FD or writing to temp file failed: " + e;
- return false;
- } finally {
- safeClose(inputStream);
- }
-
- return true;
- }
-
- /**
- * Quietly close a closeable resource (e.g. a stream or file). The input may already
- * be closed and it may even be null.
- */
- public static void safeClose(Closeable resource) {
- if (resource != null) {
- try {
- resource.close();
- } catch (IOException ioe) {
- // Catch and discard the error
- }
- }
- }
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
deleted file mode 100644
index 3daf3d831d97..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/InstallerConstants.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-/**
- * Constants for Installation / Uninstallation requests.
- * Using the same values as Finsky/Wearsky code for consistency in user analytics of failures
- */
-public class InstallerConstants {
- /** Request succeeded */
- public static final int STATUS_SUCCESS = 0;
-
- /**
- * The new PackageInstaller also returns a small set of less granular error codes, which
- * we'll remap to the range -500 and below to keep away from existing installer codes
- * (which run from -1 to -110).
- */
- public final static int ERROR_PACKAGEINSTALLER_BASE = -500;
-
- public static final int ERROR_COULD_NOT_GET_FD = -603;
- /** This node is not targeted by this request. */
-
- /** The install did not complete because could not create PackageInstaller session */
- public final static int ERROR_INSTALL_CREATE_SESSION = -612;
- /** The install did not complete because could not open PackageInstaller session */
- public final static int ERROR_INSTALL_OPEN_SESSION = -613;
- /** The install did not complete because could not open PackageInstaller output stream */
- public final static int ERROR_INSTALL_OPEN_STREAM = -614;
- /** The install did not complete because of an exception while streaming bytes */
- public final static int ERROR_INSTALL_COPY_STREAM_EXCEPTION = -615;
- /** The install did not complete because of an unexpected exception from PackageInstaller */
- public final static int ERROR_INSTALL_SESSION_EXCEPTION = -616;
- /** The install did not complete because of an unexpected userActionRequired callback */
- public final static int ERROR_INSTALL_USER_ACTION_REQUIRED = -617;
- /** The install did not complete because of an unexpected broadcast (missing fields) */
- public final static int ERROR_INSTALL_MALFORMED_BROADCAST = -618;
- /** The install did not complete because of an error while copying from downloaded file */
- public final static int ERROR_INSTALL_APK_COPY_FAILURE = -619;
- /** The install did not complete because of an error while copying to the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_COPY_STREAM = -620;
- /** The install did not complete because of an error while closing the PackageInstaller
- * output stream */
- public final static int ERROR_INSTALL_CLOSE_STREAM = -621;
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
deleted file mode 100644
index bdc22cf0e276..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerFactory.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.content.Context;
-
-/**
- * Factory that creates a Package Installer.
- */
-public class PackageInstallerFactory {
- private static PackageInstallerImpl sPackageInstaller;
-
- /**
- * Return the PackageInstaller shared object. {@code init} should have already been called.
- */
- public synchronized static PackageInstallerImpl getPackageInstaller(Context context) {
- if (sPackageInstaller == null) {
- sPackageInstaller = new PackageInstallerImpl(context);
- }
- return sPackageInstaller;
- }
-} \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
deleted file mode 100644
index 1e37f15f714d..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ /dev/null
@@ -1,327 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Implementation of package manager installation using modern PackageInstaller api.
- *
- * Heavily copied from Wearsky/Finsky implementation
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class PackageInstallerImpl {
- private static final String TAG = "PackageInstallerImpl";
-
- /** Intent actions used for broadcasts from PackageInstaller back to the local receiver */
- private static final String ACTION_INSTALL_COMMIT =
- "com.android.vending.INTENT_PACKAGE_INSTALL_COMMIT";
-
- private final Context mContext;
- private final PackageInstaller mPackageInstaller;
- private final Map<String, PackageInstaller.SessionInfo> mSessionInfoMap;
- private final Map<String, PackageInstaller.Session> mOpenSessionMap;
-
- public PackageInstallerImpl(Context context) {
- mContext = context.getApplicationContext();
- mPackageInstaller = mContext.getPackageManager().getPackageInstaller();
-
- // Capture a map of known sessions
- // This list will be pruned a bit later (stale sessions will be canceled)
- mSessionInfoMap = new HashMap<String, PackageInstaller.SessionInfo>();
- List<PackageInstaller.SessionInfo> mySessions = mPackageInstaller.getMySessions();
- for (int i = 0; i < mySessions.size(); i++) {
- PackageInstaller.SessionInfo sessionInfo = mySessions.get(i);
- String packageName = sessionInfo.getAppPackageName();
- PackageInstaller.SessionInfo oldInfo = mSessionInfoMap.put(packageName, sessionInfo);
-
- // Checking for old info is strictly for logging purposes
- if (oldInfo != null) {
- Log.w(TAG, "Multiple sessions for " + packageName + " found. Removing " + oldInfo
- .getSessionId() + " & keeping " + mySessions.get(i).getSessionId());
- }
- }
- mOpenSessionMap = new HashMap<String, PackageInstaller.Session>();
- }
-
- /**
- * This callback will be made after an installation attempt succeeds or fails.
- */
- public interface InstallListener {
- /**
- * This callback signals that preflight checks have succeeded and installation
- * is beginning.
- */
- void installBeginning();
-
- /**
- * This callback signals that installation has completed.
- */
- void installSucceeded();
-
- /**
- * This callback signals that installation has failed.
- */
- void installFailed(int errorCode, String errorDesc);
- }
-
- /**
- * This is a placeholder implementation that bundles an entire "session" into a single
- * call. This will be replaced by more granular versions that allow longer session lifetimes,
- * download progress tracking, etc.
- *
- * This must not be called on main thread.
- */
- public void install(final String packageName, ParcelFileDescriptor parcelFileDescriptor,
- final InstallListener callback) {
- // 0. Generic try/catch block because I am not really sure what exceptions (other than
- // IOException) might be thrown by PackageInstaller and I want to handle them
- // at least slightly gracefully.
- try {
- // 1. Create or recover a session, and open it
- // Try recovery first
- PackageInstaller.Session session = null;
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo != null) {
- // See if it's openable, or already held open
- session = getSession(packageName);
- }
- // If open failed, or there was no session, create a new one and open it.
- // If we cannot create or open here, the failure is terminal.
- if (session == null) {
- try {
- innerCreateSession(packageName);
- } catch (IOException ioe) {
- Log.e(TAG, "Can't create session for " + packageName + ": " + ioe.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_CREATE_SESSION,
- "Could not create session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- sessionInfo = mSessionInfoMap.get(packageName);
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- mOpenSessionMap.put(packageName, session);
- } catch (SecurityException se) {
- Log.e(TAG, "Can't open session for " + packageName + ": " + se.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_OPEN_SESSION,
- "Can't open session");
- mSessionInfoMap.remove(packageName);
- return;
- }
- }
-
- // 2. Launch task to handle file operations.
- InstallTask task = new InstallTask( mContext, packageName, parcelFileDescriptor,
- callback, session,
- getCommitCallback(packageName, sessionInfo.getSessionId(), callback));
- task.execute();
- if (task.isError()) {
- cancelSession(sessionInfo.getSessionId(), packageName);
- }
- } catch (Exception e) {
- Log.e(TAG, "Unexpected exception while installing: " + packageName + ": "
- + e.getMessage());
- callback.installFailed(InstallerConstants.ERROR_INSTALL_SESSION_EXCEPTION,
- "Unexpected exception while installing " + packageName);
- }
- }
-
- /**
- * Retrieve an existing session. Will open if needed, but does not attempt to create.
- */
- private PackageInstaller.Session getSession(String packageName) {
- // Check for already-open session
- PackageInstaller.Session session = mOpenSessionMap.get(packageName);
- if (session != null) {
- try {
- // Probe the session to ensure that it's still open. This may or may not
- // throw (if non-open), but it may serve as a canary for stale sessions.
- session.getNames();
- return session;
- } catch (IOException ioe) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + ioe.getMessage());
- mOpenSessionMap.remove(packageName);
- } catch (SecurityException se) {
- Log.e(TAG, "Stale open session for " + packageName + ": " + se.getMessage());
- mOpenSessionMap.remove(packageName);
- }
- }
- // Check to see if this is a known session
- PackageInstaller.SessionInfo sessionInfo = mSessionInfoMap.get(packageName);
- if (sessionInfo == null) {
- return null;
- }
- // Try to open it. If we fail here, assume that the SessionInfo was stale.
- try {
- session = mPackageInstaller.openSession(sessionInfo.getSessionId());
- } catch (SecurityException se) {
- Log.w(TAG, "SessionInfo was stale for " + packageName + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- } catch (IOException ioe) {
- Log.w(TAG, "IOException opening old session for " + ioe.getMessage()
- + " - deleting info");
- mSessionInfoMap.remove(packageName);
- return null;
- }
- mOpenSessionMap.put(packageName, session);
- return session;
- }
-
- /** This version throws an IOException when the session cannot be created */
- private void innerCreateSession(String packageName) throws IOException {
- if (mSessionInfoMap.containsKey(packageName)) {
- Log.w(TAG, "Creating session for " + packageName + " when one already exists");
- return;
- }
- PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
- PackageInstaller.SessionParams.MODE_FULL_INSTALL);
- params.setAppPackageName(packageName);
-
- // IOException may be thrown at this point
- int sessionId = mPackageInstaller.createSession(params);
- PackageInstaller.SessionInfo sessionInfo = mPackageInstaller.getSessionInfo(sessionId);
- mSessionInfoMap.put(packageName, sessionInfo);
- }
-
- /**
- * Cancel a session based on its sessionId. Package name is for logging only.
- */
- private void cancelSession(int sessionId, String packageName) {
- // Close if currently held open
- closeSession(packageName);
- // Remove local record
- mSessionInfoMap.remove(packageName);
- try {
- mPackageInstaller.abandonSession(sessionId);
- } catch (SecurityException se) {
- // The session no longer exists, so we can exit quietly.
- return;
- }
- }
-
- /**
- * Close a session if it happens to be held open.
- */
- private void closeSession(String packageName) {
- PackageInstaller.Session session = mOpenSessionMap.remove(packageName);
- if (session != null) {
- // Unfortunately close() is not idempotent. Try our best to make this safe.
- try {
- session.close();
- } catch (Exception e) {
- Log.w(TAG, "Unexpected error closing session for " + packageName + ": "
- + e.getMessage());
- }
- }
- }
-
- /**
- * Creates a commit callback for the package install that's underway. This will be called
- * some time after calling session.commit() (above).
- */
- private IntentSender getCommitCallback(final String packageName, final int sessionId,
- final InstallListener callback) {
- // Create a single-use broadcast receiver
- BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mContext.unregisterReceiver(this);
- handleCommitCallback(intent, packageName, sessionId, callback);
- }
- };
- // Create a matching intent-filter and register the receiver
- String action = ACTION_INSTALL_COMMIT + "." + packageName;
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(action);
- mContext.registerReceiver(broadcastReceiver, intentFilter,
- Context.RECEIVER_EXPORTED);
-
- // Create a matching PendingIntent and use it to generate the IntentSender
- Intent broadcastIntent = new Intent(action).setPackage(mContext.getPackageName());
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, packageName.hashCode(),
- broadcastIntent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
- return pendingIntent.getIntentSender();
- }
-
- /**
- * Examine the extras to determine information about the package update/install, decode
- * the result, and call the appropriate callback.
- *
- * @param intent The intent, which the PackageInstaller will have added Extras to
- * @param packageName The package name we created the receiver for
- * @param sessionId The session Id we created the receiver for
- * @param callback The callback to report success/failure to
- */
- private void handleCommitCallback(Intent intent, String packageName, int sessionId,
- InstallListener callback) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installation of " + packageName + " finished with extras "
- + intent.getExtras());
- }
- String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
- int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, Integer.MIN_VALUE);
- if (status == PackageInstaller.STATUS_SUCCESS) {
- cancelSession(sessionId, packageName);
- callback.installSucceeded();
- } else if (status == -1 /*PackageInstaller.STATUS_USER_ACTION_REQUIRED*/) {
- // TODO - use the constant when the correct/final name is in the SDK
- // TODO This is unexpected, so we are treating as failure for now
- cancelSession(sessionId, packageName);
- callback.installFailed(InstallerConstants.ERROR_INSTALL_USER_ACTION_REQUIRED,
- "Unexpected: user action required");
- } else {
- cancelSession(sessionId, packageName);
- int errorCode = getPackageManagerErrorCode(status);
- Log.e(TAG, "Error " + errorCode + " while installing " + packageName + ": "
- + statusMessage);
- callback.installFailed(errorCode, null);
- }
- }
-
- private int getPackageManagerErrorCode(int status) {
- // This is a hack: because PackageInstaller now reports error codes
- // with small positive values, we need to remap them into a space
- // that is more compatible with the existing package manager error codes.
- // See https://sites.google.com/a/google.com/universal-store/documentation
- // /android-client/download-error-codes
- int errorCode;
- if (status == Integer.MIN_VALUE) {
- errorCode = InstallerConstants.ERROR_INSTALL_MALFORMED_BROADCAST;
- } else {
- errorCode = InstallerConstants.ERROR_PACKAGEINSTALLER_BASE - status;
- }
- return errorCode;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
deleted file mode 100644
index 2c289b2a6f94..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageArgs.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-/**
- * Installation Util that contains a list of parameters that are needed for
- * installing/uninstalling.
- */
-public class WearPackageArgs {
- private static final String KEY_PACKAGE_NAME =
- "com.google.android.clockwork.EXTRA_PACKAGE_NAME";
- private static final String KEY_ASSET_URI =
- "com.google.android.clockwork.EXTRA_ASSET_URI";
- private static final String KEY_START_ID =
- "com.google.android.clockwork.EXTRA_START_ID";
- private static final String KEY_PERM_URI =
- "com.google.android.clockwork.EXTRA_PERM_URI";
- private static final String KEY_CHECK_PERMS =
- "com.google.android.clockwork.EXTRA_CHECK_PERMS";
- private static final String KEY_SKIP_IF_SAME_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_SAME_VERSION";
- private static final String KEY_COMPRESSION_ALG =
- "com.google.android.clockwork.EXTRA_KEY_COMPRESSION_ALG";
- private static final String KEY_COMPANION_SDK_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_SDK_VERSION";
- private static final String KEY_COMPANION_DEVICE_VERSION =
- "com.google.android.clockwork.EXTRA_KEY_COMPANION_DEVICE_VERSION";
- private static final String KEY_SHOULD_CHECK_GMS_DEPENDENCY =
- "com.google.android.clockwork.EXTRA_KEY_SHOULD_CHECK_GMS_DEPENDENCY";
- private static final String KEY_SKIP_IF_LOWER_VERSION =
- "com.google.android.clockwork.EXTRA_SKIP_IF_LOWER_VERSION";
-
- public static String getPackageName(Bundle b) {
- return b.getString(KEY_PACKAGE_NAME);
- }
-
- public static Bundle setPackageName(Bundle b, String packageName) {
- b.putString(KEY_PACKAGE_NAME, packageName);
- return b;
- }
-
- public static Uri getAssetUri(Bundle b) {
- return b.getParcelable(KEY_ASSET_URI);
- }
-
- public static Uri getPermUri(Bundle b) {
- return b.getParcelable(KEY_PERM_URI);
- }
-
- public static boolean checkPerms(Bundle b) {
- return b.getBoolean(KEY_CHECK_PERMS);
- }
-
- public static boolean skipIfSameVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_SAME_VERSION);
- }
-
- public static int getCompanionSdkVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_SDK_VERSION);
- }
-
- public static int getCompanionDeviceVersion(Bundle b) {
- return b.getInt(KEY_COMPANION_DEVICE_VERSION);
- }
-
- public static String getCompressionAlg(Bundle b) {
- return b.getString(KEY_COMPRESSION_ALG);
- }
-
- public static int getStartId(Bundle b) {
- return b.getInt(KEY_START_ID);
- }
-
- public static boolean skipIfLowerVersion(Bundle b) {
- return b.getBoolean(KEY_SKIP_IF_LOWER_VERSION, false);
- }
-
- public static Bundle setStartId(Bundle b, int startId) {
- b.putInt(KEY_START_ID, startId);
- return b;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
deleted file mode 100644
index 02b9d298db0e..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageIconProvider.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.annotation.TargetApi;
-import android.app.ActivityManager;
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.List;
-
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
-public class WearPackageIconProvider extends ContentProvider {
- private static final String TAG = "WearPackageIconProvider";
- public static final String AUTHORITY = "com.google.android.packageinstaller.wear.provider";
-
- private static final String REQUIRED_PERMISSION =
- "com.google.android.permission.INSTALL_WEARABLE_PACKAGES";
-
- /** MIME types. */
- public static final String ICON_TYPE = "vnd.android.cursor.item/cw_package_icon";
-
- @Override
- public boolean onCreate() {
- return true;
- }
-
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
- throw new UnsupportedOperationException("Query is not supported.");
- }
-
- @Override
- public String getType(Uri uri) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- if (AUTHORITY.equals(uri.getEncodedAuthority())) {
- return ICON_TYPE;
- }
- return null;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException("Insert is not supported.");
- }
-
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- file.delete();
- }
- }
-
- return 0;
- }
-
- @Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException("Update is not supported.");
- }
-
- @Override
- public ParcelFileDescriptor openFile(
- Uri uri, @SuppressWarnings("unused") String mode) throws FileNotFoundException {
- if (uri == null) {
- throw new IllegalArgumentException("URI passed in is null.");
- }
-
- enforcePermissions(uri);
-
- if (ICON_TYPE.equals(getType(uri))) {
- final File file = WearPackageUtil.getIconFile(
- this.getContext().getApplicationContext(), getPackageNameFromUri(uri));
- if (file != null) {
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- }
- }
- return null;
- }
-
- public static Uri getUriForPackage(final String packageName) {
- return Uri.parse("content://" + AUTHORITY + "/icons/" + packageName + ".icon");
- }
-
- private String getPackageNameFromUri(Uri uri) {
- if (uri == null) {
- return null;
- }
- List<String> pathSegments = uri.getPathSegments();
- String packageName = pathSegments.get(pathSegments.size() - 1);
-
- if (packageName.endsWith(".icon")) {
- packageName = packageName.substring(0, packageName.lastIndexOf("."));
- }
- return packageName;
- }
-
- /**
- * Make sure the calling app is either a system app or the same app or has the right permission.
- * @throws SecurityException if the caller has insufficient permissions.
- */
- @TargetApi(Build.VERSION_CODES.BASE_1_1)
- private void enforcePermissions(Uri uri) {
- // Redo some of the permission check in {@link ContentProvider}. Just add an extra check to
- // allow System process to access this provider.
- Context context = getContext();
- final int pid = Binder.getCallingPid();
- final int uid = Binder.getCallingUid();
- final int myUid = android.os.Process.myUid();
-
- if (uid == myUid || isSystemApp(context, pid)) {
- return;
- }
-
- if (context.checkPermission(REQUIRED_PERMISSION, pid, uid) == PERMISSION_GRANTED) {
- return;
- }
-
- // last chance, check against any uri grants
- if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
- == PERMISSION_GRANTED) {
- return;
- }
-
- throw new SecurityException("Permission Denial: reading "
- + getClass().getName() + " uri " + uri + " from pid=" + pid
- + ", uid=" + uid);
- }
-
- /**
- * From the pid of the calling process, figure out whether this is a system app or not. We do
- * this by checking the application information corresponding to the pid and then checking if
- * FLAG_SYSTEM is set.
- */
- @TargetApi(Build.VERSION_CODES.CUPCAKE)
- private boolean isSystemApp(Context context, int pid) {
- // Get the Activity Manager Object
- ActivityManager aManager =
- (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
- // Get the list of running Applications
- List<ActivityManager.RunningAppProcessInfo> rapInfoList =
- aManager.getRunningAppProcesses();
- for (ActivityManager.RunningAppProcessInfo rapInfo : rapInfoList) {
- if (rapInfo.pid == pid) {
- try {
- PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(
- rapInfo.pkgList[0], 0);
- if (pkgInfo != null && pkgInfo.applicationInfo != null &&
- (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
- Log.d(TAG, pid + " is a system app.");
- return true;
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Could not find package information.", e);
- return false;
- }
- }
- }
- return false;
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
deleted file mode 100644
index ae0f4ece1c17..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ /dev/null
@@ -1,621 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.Process;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-import androidx.annotation.Nullable;
-import com.android.packageinstaller.DeviceUtils;
-import com.android.packageinstaller.PackageUtil;
-import com.android.packageinstaller.R;
-import com.android.packageinstaller.common.EventResultPersister;
-import com.android.packageinstaller.common.UninstallEventReceiver;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Service that will install/uninstall packages. It will check for permissions and features as well.
- *
- * -----------
- *
- * Debugging information:
- *
- * Install Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.INSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * --eu com.google.android.clockwork.EXTRA_ASSET_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/wearable/com.google.android.gms/apk \
- * --es android.intent.extra.INSTALLER_PACKAGE_NAME com.google.android.gms \
- * --ez com.google.android.clockwork.EXTRA_CHECK_PERMS false \
- * --eu com.google.android.clockwork.EXTRA_PERM_URI content://com.google.android.clockwork.home.provider/host/com.google.android.wearable.app/permissions \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Uninstall Action example:
- * adb shell am startservice -a com.android.packageinstaller.wear.UNINSTALL_PACKAGE \
- * -d package://com.google.android.gms \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- *
- * Retry GMS:
- * adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
- * com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
- */
-public class WearPackageInstallerService extends Service
- implements EventResultPersister.EventResultObserver {
- private static final String TAG = "WearPkgInstallerService";
-
- private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
- private static final String BROADCAST_ACTION =
- "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
-
- private final int START_INSTALL = 1;
- private final int START_UNINSTALL = 2;
-
- private int mInstallNotificationId = 1;
- private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
- private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
-
- private class UninstallParams {
- public String mPackageName;
- public PowerManager.WakeLock mLock;
-
- UninstallParams(String packageName, PowerManager.WakeLock lock) {
- mPackageName = packageName;
- mLock = lock;
- }
- }
-
- private final class ServiceHandler extends Handler {
- public ServiceHandler(Looper looper) {
- super(looper);
- }
-
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case START_INSTALL:
- installPackage(msg.getData());
- break;
- case START_UNINSTALL:
- uninstallPackage(msg.getData());
- break;
- }
- }
- }
- private ServiceHandler mServiceHandler;
- private NotificationChannel mNotificationChannel;
- private static volatile PowerManager.WakeLock lockStatic = null;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- HandlerThread thread = new HandlerThread("PackageInstallerThread",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
-
- mServiceHandler = new ServiceHandler(thread.getLooper());
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (!DeviceUtils.isWear(this)) {
- Log.w(TAG, "Not running on wearable.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (intent == null) {
- Log.w(TAG, "Got null intent.");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Got install/uninstall request " + intent);
- }
-
- Uri packageUri = intent.getData();
- if (packageUri == null) {
- Log.e(TAG, "No package URI in intent");
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- final String packageName = WearPackageUtil.getSanitizedPackageName(packageUri);
- if (packageName == null) {
- Log.e(TAG, "Invalid package name in URI (expected package:<pkgName>): " + packageUri);
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- if (!lock.isHeld()) {
- lock.acquire();
- }
-
- Bundle intentBundle = intent.getExtras();
- if (intentBundle == null) {
- intentBundle = new Bundle();
- }
- WearPackageArgs.setStartId(intentBundle, startId);
- WearPackageArgs.setPackageName(intentBundle, packageName);
- Message msg;
- String notifTitle;
- if (Intent.ACTION_INSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_INSTALL);
- notifTitle = getString(R.string.installing);
- } else if (Intent.ACTION_UNINSTALL_PACKAGE.equals(intent.getAction())) {
- msg = mServiceHandler.obtainMessage(START_UNINSTALL);
- notifTitle = getString(R.string.uninstalling);
- } else {
- Log.e(TAG, "Unknown action : " + intent.getAction());
- finishServiceEarly(startId);
- return START_NOT_STICKY;
- }
- Pair<Integer, Notification> notifPair = buildNotification(packageName, notifTitle);
- startForeground(notifPair.first, notifPair.second);
- msg.setData(intentBundle);
- mServiceHandler.sendMessage(msg);
- return START_NOT_STICKY;
- }
-
- private void installPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
- final Uri assetUri = WearPackageArgs.getAssetUri(argsBundle);
- final Uri permUri = WearPackageArgs.getPermUri(argsBundle);
- boolean checkPerms = WearPackageArgs.checkPerms(argsBundle);
- boolean skipIfSameVersion = WearPackageArgs.skipIfSameVersion(argsBundle);
- int companionSdkVersion = WearPackageArgs.getCompanionSdkVersion(argsBundle);
- int companionDeviceVersion = WearPackageArgs.getCompanionDeviceVersion(argsBundle);
- String compressionAlg = WearPackageArgs.getCompressionAlg(argsBundle);
- boolean skipIfLowerVersion = WearPackageArgs.skipIfLowerVersion(argsBundle);
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Installing package: " + packageName + ", assetUri: " + assetUri +
- ",permUri: " + permUri + ", startId: " + startId + ", checkPerms: " +
- checkPerms + ", skipIfSameVersion: " + skipIfSameVersion +
- ", compressionAlg: " + compressionAlg + ", companionSdkVersion: " +
- companionSdkVersion + ", companionDeviceVersion: " + companionDeviceVersion +
- ", skipIfLowerVersion: " + skipIfLowerVersion);
- }
- final PackageManager pm = getPackageManager();
- File tempFile = null;
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
- boolean messageSent = false;
- try {
- PackageInfo existingPkgInfo = null;
- try {
- existingPkgInfo = pm.getPackageInfo(packageName,
- PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
- if (existingPkgInfo != null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Replacing package:" + packageName);
- }
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore this exception. We could not find the package, will treat as a new
- // installation.
- }
- // TODO(28021618): This was left as a temp file due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this permission logic might want to be
- // reworked to use a stream instead of a file so that we don't need to write a
- // file at all. Note that there might be some trickiness with opening a stream
- // for multiple users.
- ParcelFileDescriptor parcelFd = getContentResolver()
- .openFileDescriptor(assetUri, "r");
- tempFile = WearPackageUtil.getFileFromFd(WearPackageInstallerService.this,
- parcelFd, packageName, compressionAlg);
- if (tempFile == null) {
- Log.e(TAG, "Could not create a temp file from FD for " + packageName);
- return;
- }
- PackageInfo pkgInfo = PackageUtil.getPackageInfo(this, tempFile,
- PackageManager.GET_PERMISSIONS | PackageManager.GET_CONFIGURATIONS);
- if (pkgInfo == null) {
- Log.e(TAG, "Could not parse apk information for " + packageName);
- return;
- }
-
- if (!pkgInfo.packageName.equals(packageName)) {
- Log.e(TAG, "Wearable Package Name has to match what is provided for " +
- packageName);
- return;
- }
-
- ApplicationInfo appInfo = pkgInfo.applicationInfo;
- appInfo.sourceDir = tempFile.getPath();
- appInfo.publicSourceDir = tempFile.getPath();
- getLabelAndUpdateNotification(packageName,
- getString(R.string.installing_app, appInfo.loadLabel(pm)));
-
- List<String> wearablePerms = Arrays.asList(pkgInfo.requestedPermissions);
-
- // Log if the installed pkg has a higher version number.
- if (existingPkgInfo != null) {
- long longVersionCode = pkgInfo.getLongVersionCode();
- if (existingPkgInfo.getLongVersionCode() == longVersionCode) {
- if (skipIfSameVersion) {
- Log.w(TAG, "Version number (" + longVersionCode +
- ") of new app is equal to existing app for " + packageName +
- "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is equal to existing app for " + packageName);
- }
- } else if (existingPkgInfo.getLongVersionCode() > longVersionCode) {
- if (skipIfLowerVersion) {
- // Starting in Feldspar, we are not going to allow downgrades of any app.
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() +
- ") for " + packageName + "; not installing due to versionCheck");
- return;
- } else {
- Log.w(TAG, "Version number of new app (" + longVersionCode +
- ") is lower than existing app ( "
- + existingPkgInfo.getLongVersionCode() + ") for " + packageName);
- }
- }
-
- // Following the Android Phone model, we should only check for permissions for any
- // newly defined perms.
- if (existingPkgInfo.requestedPermissions != null) {
- for (int i = 0; i < existingPkgInfo.requestedPermissions.length; ++i) {
- // If the permission is granted, then we will not ask to request it again.
- if ((existingPkgInfo.requestedPermissionsFlags[i] &
- PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, existingPkgInfo.requestedPermissions[i] +
- " is already granted for " + packageName);
- }
- wearablePerms.remove(existingPkgInfo.requestedPermissions[i]);
- }
- }
- }
- }
-
- // Check that the wearable has all the features.
- boolean hasAllFeatures = true;
- for (FeatureInfo feature : pkgInfo.reqFeatures) {
- if (feature.name != null && !pm.hasSystemFeature(feature.name) &&
- (feature.flags & FeatureInfo.FLAG_REQUIRED) != 0) {
- Log.e(TAG, "Wearable does not have required feature: " + feature +
- " for " + packageName);
- hasAllFeatures = false;
- }
- }
-
- if (!hasAllFeatures) {
- return;
- }
-
- // Check permissions on both the new wearable package and also on the already installed
- // wearable package.
- // If the app is targeting API level 23, we will also start a service in ClockworkHome
- // which will ultimately prompt the user to accept/reject permissions.
- if (checkPerms && !checkPermissions(pkgInfo, companionSdkVersion,
- companionDeviceVersion, permUri, wearablePerms, tempFile)) {
- Log.w(TAG, "Wearable does not have enough permissions.");
- return;
- }
-
- // Finally install the package.
- ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(assetUri, "r");
- PackageInstallerFactory.getPackageInstaller(this).install(packageName, fd,
- new PackageInstallListener(this, lock, startId, packageName));
-
- messageSent = true;
- Log.i(TAG, "Sent installation request for " + packageName);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Could not find the file with URI " + assetUri, e);
- } finally {
- if (!messageSent) {
- // Some error happened. If the message has been sent, we can wait for the observer
- // which will finish the service.
- if (tempFile != null) {
- tempFile.delete();
- }
- finishService(lock, startId);
- }
- }
- }
-
- // TODO: This was left using the old PackageManager API due to the fact that this code is being
- // deprecated and that we need the bare minimum to continue working moving forward
- // If this code is used as reference, this logic should be reworked to use the new
- // PackageInstaller APIs similar to how installPackage was reworked
- private void uninstallPackage(Bundle argsBundle) {
- int startId = WearPackageArgs.getStartId(argsBundle);
- final String packageName = WearPackageArgs.getPackageName(argsBundle);
-
- PowerManager.WakeLock lock = getLock(this.getApplicationContext());
-
- UninstallParams params = new UninstallParams(packageName, lock);
- mServiceIdToParams.put(startId, params);
-
- final PackageManager pm = getPackageManager();
- try {
- PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
- getLabelAndUpdateNotification(packageName,
- getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
-
- int uninstallId = UninstallEventReceiver.addObserver(this,
- EventResultPersister.GENERATE_NEW_ID, this);
-
- Intent broadcastIntent = new Intent(BROADCAST_ACTION);
- broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
- broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
- broadcastIntent.setPackage(getPackageName());
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
- broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_MUTABLE);
-
- // Found package, send uninstall request.
- pm.getPackageInstaller().uninstall(
- new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
- PackageManager.DELETE_ALL_USERS,
- pendingIntent.getIntentSender());
-
- Log.i(TAG, "Sent delete request for " + packageName);
- } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
- // Couldn't find the package, no need to call uninstall.
- Log.w(TAG, "Could not find package, not deleting " + packageName, e);
- finishService(lock, startId);
- } catch (EventResultPersister.OutOfIdsException e) {
- Log.e(TAG, "Fails to start uninstall", e);
- finishService(lock, startId);
- }
- }
-
- @Override
- public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
- if (mServiceIdToParams.containsKey(serviceId)) {
- UninstallParams params = mServiceIdToParams.get(serviceId);
- try {
- if (status == PackageInstaller.STATUS_SUCCESS) {
- Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
- } else {
- Log.e(TAG, "Package uninstall failed " + params.mPackageName
- + ", returnCode " + legacyStatus);
- }
- } finally {
- finishService(params.mLock, serviceId);
- }
- }
- }
-
- private boolean checkPermissions(PackageInfo pkgInfo, int companionSdkVersion,
- int companionDeviceVersion, Uri permUri, List<String> wearablePermissions,
- File apkFile) {
- // Assumption: We are running on Android O.
- // If the Phone App is targeting M, all permissions may not have been granted to the phone
- // app. If the Wear App is then not targeting M, there may be permissions that are not
- // granted on the Phone app (by the user) right now and we cannot just grant it for the Wear
- // app.
- if (pkgInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.M) {
- // Install the app if Wear App is ready for the new perms model.
- return true;
- }
-
- if (!doesWearHaveUngrantedPerms(pkgInfo.packageName, permUri, wearablePermissions)) {
- // All permissions requested by the watch are already granted on the phone, no need
- // to do anything.
- return true;
- }
-
- // Log an error if Wear is targeting < 23 and phone is targeting >= 23.
- if (companionSdkVersion == 0 || companionSdkVersion >= Build.VERSION_CODES.M) {
- Log.e(TAG, "MNC: Wear app's targetSdkVersion should be at least 23, if "
- + "phone app is targeting at least 23, will continue.");
- }
-
- return false;
- }
-
- /**
- * Given a {@string packageName} corresponding to a phone app, query the provider for all the
- * perms that are granted.
- *
- * @return true if the Wear App has any perms that have not been granted yet on the phone side.
- * @return true if there is any error cases.
- */
- private boolean doesWearHaveUngrantedPerms(String packageName, Uri permUri,
- List<String> wearablePermissions) {
- if (permUri == null) {
- Log.e(TAG, "Permission URI is null");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
- Cursor permCursor = getContentResolver().query(permUri, null, null, null, null);
- if (permCursor == null) {
- Log.e(TAG, "Could not get the cursor for the permissions");
- // Pretend there is an ungranted permission to avoid installing for error cases.
- return true;
- }
-
- Set<String> grantedPerms = new HashSet<>();
- Set<String> ungrantedPerms = new HashSet<>();
- while(permCursor.moveToNext()) {
- // Make sure that the MatrixCursor returned by the ContentProvider has 2 columns and
- // verify their types.
- if (permCursor.getColumnCount() == 2
- && Cursor.FIELD_TYPE_STRING == permCursor.getType(0)
- && Cursor.FIELD_TYPE_INTEGER == permCursor.getType(1)) {
- String perm = permCursor.getString(0);
- Integer granted = permCursor.getInt(1);
- if (granted == 1) {
- grantedPerms.add(perm);
- } else {
- ungrantedPerms.add(perm);
- }
- }
- }
- permCursor.close();
-
- boolean hasUngrantedPerm = false;
- for (String wearablePerm : wearablePermissions) {
- if (!grantedPerms.contains(wearablePerm)) {
- hasUngrantedPerm = true;
- if (!ungrantedPerms.contains(wearablePerm)) {
- // This is an error condition. This means that the wearable has permissions that
- // are not even declared in its host app. This is a developer error.
- Log.e(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm
- + "\" that is not defined in the host application's manifest.");
- } else {
- Log.w(TAG, "Wearable " + packageName + " has a permission \"" + wearablePerm +
- "\" that is not granted in the host application.");
- }
- }
- }
- return hasUngrantedPerm;
- }
-
- /** Finishes the service after fulfilling obligation to call startForeground. */
- private void finishServiceEarly(int startId) {
- Pair<Integer, Notification> notifPair = buildNotification(
- getApplicationContext().getPackageName(), "");
- startForeground(notifPair.first, notifPair.second);
- finishService(null, startId);
- }
-
- private void finishService(PowerManager.WakeLock lock, int startId) {
- if (lock != null && lock.isHeld()) {
- lock.release();
- }
- stopSelf(startId);
- }
-
- private synchronized PowerManager.WakeLock getLock(Context context) {
- if (lockStatic == null) {
- PowerManager mgr =
- (PowerManager) context.getSystemService(Context.POWER_SERVICE);
- lockStatic = mgr.newWakeLock(
- PowerManager.PARTIAL_WAKE_LOCK, context.getClass().getSimpleName());
- lockStatic.setReferenceCounted(true);
- }
- return lockStatic;
- }
-
- private class PackageInstallListener implements PackageInstallerImpl.InstallListener {
- private Context mContext;
- private PowerManager.WakeLock mWakeLock;
- private int mStartId;
- private String mApplicationPackageName;
- private PackageInstallListener(Context context, PowerManager.WakeLock wakeLock,
- int startId, String applicationPackageName) {
- mContext = context;
- mWakeLock = wakeLock;
- mStartId = startId;
- mApplicationPackageName = applicationPackageName;
- }
-
- @Override
- public void installBeginning() {
- Log.i(TAG, "Package " + mApplicationPackageName + " is being installed.");
- }
-
- @Override
- public void installSucceeded() {
- try {
- Log.i(TAG, "Package " + mApplicationPackageName + " was installed.");
-
- // Delete tempFile from the file system.
- File tempFile = WearPackageUtil.getTemporaryFile(mContext, mApplicationPackageName);
- if (tempFile != null) {
- tempFile.delete();
- }
- } finally {
- finishService(mWakeLock, mStartId);
- }
- }
-
- @Override
- public void installFailed(int errorCode, String errorDesc) {
- Log.e(TAG, "Package install failed " + mApplicationPackageName
- + ", errorCode " + errorCode);
- finishService(mWakeLock, mStartId);
- }
- }
-
- private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
- final String title) {
- int notifId;
- if (mNotifIdMap.containsKey(packageName)) {
- notifId = mNotifIdMap.get(packageName);
- } else {
- notifId = mInstallNotificationId++;
- mNotifIdMap.put(packageName, notifId);
- }
-
- if (mNotificationChannel == null) {
- mNotificationChannel = new NotificationChannel(WEAR_APPS_CHANNEL,
- getString(R.string.wear_app_channel), NotificationManager.IMPORTANCE_MIN);
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(mNotificationChannel);
- }
- return new Pair<>(notifId, new Notification.Builder(this, WEAR_APPS_CHANNEL)
- .setSmallIcon(R.drawable.ic_file_download)
- .setContentTitle(title)
- .build());
- }
-
- private void getLabelAndUpdateNotification(String packageName, String title) {
- // Update notification since we have a label now.
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- Pair<Integer, Notification> notifPair = buildNotification(packageName, title);
- notificationManager.notify(notifPair.first, notifPair.second);
- }
-}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
deleted file mode 100644
index 6a9145db9a06..000000000000
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageUtil.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2015 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.packageinstaller.wear;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.tukaani.xz.LZMAInputStream;
-import org.tukaani.xz.XZInputStream;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class WearPackageUtil {
- private static final String TAG = "WearablePkgInstaller";
-
- private static final String COMPRESSION_LZMA = "lzma";
- private static final String COMPRESSION_XZ = "xz";
-
- public static File getTemporaryFile(Context context, String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "tmp");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- File newFile = new File(newFileDir, packageName + ".apk");
- return newFile;
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- public static File getIconFile(final Context context, final String packageName) {
- try {
- File newFileDir = new File(context.getFilesDir(), "images/icons");
- newFileDir.mkdirs();
- Os.chmod(newFileDir.getAbsolutePath(), 0771);
- return new File(newFileDir, packageName + ".icon");
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to open.", e);
- return null;
- }
- }
-
- /**
- * In order to make sure that the Wearable Asset Manager has a reasonable apk that can be used
- * by the PackageManager, we will parse it before sending it to the PackageManager.
- * Unfortunately, ParsingPackageUtils needs a file to parse. So, we have to temporarily convert
- * the fd to a File.
- *
- * @param context
- * @param fd FileDescriptor to convert to File
- * @param packageName Name of package, will define the name of the file
- * @param compressionAlg Can be null. For ALT mode the APK will be compressed. We will
- * decompress it here
- */
- public static File getFileFromFd(Context context, ParcelFileDescriptor fd,
- String packageName, String compressionAlg) {
- File newFile = getTemporaryFile(context, packageName);
- if (fd == null || fd.getFileDescriptor() == null) {
- return null;
- }
- InputStream fr = new ParcelFileDescriptor.AutoCloseInputStream(fd);
- try {
- if (TextUtils.equals(compressionAlg, COMPRESSION_XZ)) {
- fr = new XZInputStream(fr);
- } else if (TextUtils.equals(compressionAlg, COMPRESSION_LZMA)) {
- fr = new LZMAInputStream(fr);
- }
- } catch (IOException e) {
- Log.e(TAG, "Compression was set to " + compressionAlg + ", but could not decode ", e);
- return null;
- }
-
- int nRead;
- byte[] data = new byte[1024];
- try {
- final FileOutputStream fo = new FileOutputStream(newFile);
- while ((nRead = fr.read(data, 0, data.length)) != -1) {
- fo.write(data, 0, nRead);
- }
- fo.flush();
- fo.close();
- Os.chmod(newFile.getAbsolutePath(), 0644);
- return newFile;
- } catch (IOException e) {
- Log.e(TAG, "Reading from Asset FD or writing to temp file failed ", e);
- return null;
- } catch (ErrnoException e) {
- Log.e(TAG, "Could not set permissions on file ", e);
- return null;
- } finally {
- try {
- fr.close();
- } catch (IOException e) {
- Log.e(TAG, "Failed to close the file from FD ", e);
- }
- }
- }
-
- /**
- * @return com.google.com from expected formats like
- * Uri: package:com.google.com, package:/com.google.com, package://com.google.com
- */
- public static String getSanitizedPackageName(Uri packageUri) {
- String packageName = packageUri.getEncodedSchemeSpecificPart();
- if (packageName != null) {
- return packageName.replaceAll("^/+", "");
- }
- return packageName;
- }
-}
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index d6cbf2a0f581..0cb85d8638b0 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -64,6 +64,7 @@ android_library {
srcs: [
"src/**/*.java",
"src/**/*.kt",
+ "src/**/I*.aidl",
],
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 8666584e0972..38c871c698c9 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -92,3 +92,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "volume_dialog_audio_sharing_fix"
+ namespace: "cross_device_experiences"
+ description: "Gates whether to show separate volume bars during audio sharing"
+ bug: "336716411"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java
new file mode 100644
index 000000000000..1cbb8b4faaa8
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreference.java
@@ -0,0 +1,326 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * A data class representing an action/switch preference. The preference could be one of the four
+ * following forms: 1. Texted row with action to jump to another page 2. Texted row without action
+ * 3. Texted row with action and switch 4. Texted row with switch
+ */
+public class ActionSwitchPreference extends DeviceSettingPreference implements Parcelable {
+ private final String mTitle;
+ private final String mSummary;
+ private final Bitmap mIcon;
+ private final Intent mIntent;
+ private final boolean mHasSwitch;
+ private final boolean mChecked;
+ private final boolean mIsAllowedChangingState;
+ private final Bundle mExtras;
+
+ ActionSwitchPreference(
+ String title,
+ @Nullable String summary,
+ @Nullable Bitmap icon,
+ @Nullable Intent intent,
+ boolean hasSwitch,
+ boolean checked,
+ boolean allowChangingState,
+ @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ validate(title);
+ mTitle = title;
+ mSummary = summary;
+ mIcon = icon;
+ mIntent = intent;
+ mHasSwitch = hasSwitch;
+ mChecked = checked;
+ mIsAllowedChangingState = allowChangingState;
+ mExtras = extras;
+ }
+
+ private static void validate(String title) {
+ if (Objects.isNull(title)) {
+ throw new IllegalArgumentException("Title must be set");
+ }
+ }
+
+ /**
+ * Reads an {@link ActionSwitchPreference} instance from {@link Parcel}
+ * @param in The parcel to read from
+ * @return The instance read
+ */
+ @NonNull
+ public static ActionSwitchPreference readFromParcel(@NonNull Parcel in) {
+ String title = in.readString();
+ String summary = in.readString();
+ Bitmap icon = in.readParcelable(Bitmap.class.getClassLoader());
+ Intent intent = in.readParcelable(Intent.class.getClassLoader());
+ boolean hasSwitch = in.readBoolean();
+ boolean checked = in.readBoolean();
+ boolean allowChangingState = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ActionSwitchPreference(
+ title, summary, icon, intent, hasSwitch, checked, allowChangingState, extras);
+ }
+
+ public static final Creator<ActionSwitchPreference> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ActionSwitchPreference createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ActionSwitchPreference[] newArray(int size) {
+ return new ActionSwitchPreference[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mTitle);
+ dest.writeString(mSummary);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeParcelable(mIntent, flags);
+ dest.writeBoolean(mHasSwitch);
+ dest.writeBoolean(mChecked);
+ dest.writeBoolean(mIsAllowedChangingState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ActionSwitchPreference}. */
+ public static final class Builder {
+ private String mTitle;
+ private String mSummary;
+ private Bitmap mIcon;
+ private Intent mIntent;
+ private boolean mHasSwitch;
+ private boolean mChecked;
+ private boolean mIsAllowedChangingState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the title of the preference.
+ *
+ * @param title The title of the preference.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Sets the summary of the preference, optional.
+ *
+ * @param summary The preference summary.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSummary(@Nullable String summary) {
+ mSummary = summary;
+ return this;
+ }
+
+ /**
+ * Sets the icon to be displayed on the left of the preference, optional.
+ *
+ * @param icon The icon.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIcon(@Nullable Bitmap icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the Intent to launch when the preference is clicked, optional.
+ *
+ * @param intent The Intent.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIntent(@Nullable Intent intent) {
+ mIntent = intent;
+ return this;
+ }
+
+ /**
+ * Sets whether the preference will contain a switch.
+ *
+ * @param hasSwitch Whether the preference contains a switch.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setHasSwitch(boolean hasSwitch) {
+ mHasSwitch = hasSwitch;
+ return this;
+ }
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param checked Whether the switch is checked.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setChecked(boolean checked) {
+ mChecked = checked;
+ return this;
+ }
+
+ /**
+ * Sets whether state can be changed by user.
+ *
+ * @param allowChangingState Whether user is allowed to change state.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setAllowedChangingState(boolean allowChangingState) {
+ mIsAllowedChangingState = allowChangingState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ActionSwitchPreference} object.
+ *
+ * @return Returns the built {@link ActionSwitchPreference} object.
+ */
+ @NonNull
+ public ActionSwitchPreference build() {
+ return new ActionSwitchPreference(
+ mTitle,
+ mSummary,
+ mIcon,
+ mIntent,
+ mHasSwitch,
+ mChecked,
+ mIsAllowedChangingState,
+ mExtras);
+ }
+ }
+
+ /**
+ * Gets the title of the preference.
+ *
+ * @return Returns the title of the preference.
+ */
+ @NonNull
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Gets the summary of the preference.
+ *
+ * @return Returns the summary of the preference.
+ */
+ @Nullable
+ public String getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * Gets the icon of the preference.
+ *
+ * @return Returns the icon of the preference.
+ */
+ @Nullable
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Gets the Intent to launch when the preference is clicked.
+ *
+ * @return Returns the intent to launch.
+ */
+ @Nullable
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Whether the preference contains a switch.
+ *
+ * @return Whether the preference contains a switch.
+ */
+ public boolean hasSwitch() {
+ return mHasSwitch;
+ }
+
+ /**
+ * Whether the switch is checked.
+ *
+ * @return Whether the switch is checked.
+ */
+ public boolean getChecked() {
+ return mChecked;
+ }
+
+ /**
+ * Gets whether the state can be changed by user.
+ *
+ * @return Whether the state can be changed by user.
+ */
+ public boolean isAllowedChangingState() {
+ return mIsAllowedChangingState;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
new file mode 100644
index 000000000000..91c1a59fa6f2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceState.java
@@ -0,0 +1,136 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing the state of an action/switch preference. */
+public class ActionSwitchPreferenceState extends DeviceSettingPreferenceState
+ implements Parcelable {
+ private final boolean mChecked;
+ private final Bundle mExtras;
+
+ ActionSwitchPreferenceState(boolean checked, @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ mChecked = checked;
+ mExtras = extras;
+ }
+
+ /**
+ * Reads an {@link ActionSwitchPreferenceState} instance from {@link Parcel}
+ * @param in The parcel to read from
+ * @return The instance read
+ */
+ @NonNull
+ public static ActionSwitchPreferenceState readFromParcel(@NonNull Parcel in) {
+ boolean checked = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ActionSwitchPreferenceState(checked, extras);
+ }
+
+ public static final Creator<ActionSwitchPreferenceState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ActionSwitchPreferenceState createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ActionSwitchPreferenceState[] newArray(int size) {
+ return new ActionSwitchPreferenceState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeBoolean(mChecked);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ActionSwitchPreferenceState}. */
+ public static final class Builder {
+ private boolean mChecked;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param checked Whether the switch is checked.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setChecked(boolean checked) {
+ mChecked = checked;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the object.
+ *
+ * @return Returns the built object.
+ */
+ @NonNull
+ public ActionSwitchPreferenceState build() {
+ return new ActionSwitchPreferenceState(mChecked, mExtras);
+ }
+ }
+
+ /**
+ * Whether the switch is checked.
+ *
+ * @return Whether the switch is checked.
+ */
+ public boolean getChecked() {
+ return mChecked;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl
new file mode 100644
index 000000000000..acbaf2df1d2c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceInfo; \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
new file mode 100644
index 000000000000..52e520ea8835
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfo.java
@@ -0,0 +1,137 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a bluetooth device. */
+public class DeviceInfo implements Parcelable {
+ private final String mBluetoothAddress;
+ private final Bundle mExtras;
+
+ DeviceInfo(String bluetoothAddress, Bundle extras) {
+ validate(bluetoothAddress);
+ mBluetoothAddress = bluetoothAddress;
+ mExtras = extras;
+ }
+
+ private static void validate(String bluetoothAddress) {
+ if (Objects.isNull(bluetoothAddress)) {
+ throw new IllegalArgumentException("Bluetooth address must be set");
+ }
+ }
+
+ /** Read a {@link DeviceInfo} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceInfo readFromParcel(@NonNull Parcel in) {
+ String bluetoothAddress = in.readString();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new DeviceInfo(bluetoothAddress, extras);
+ }
+
+ public static final Creator<DeviceInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceInfo createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceInfo[] newArray(int size) {
+ return new DeviceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mBluetoothAddress);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link DeviceInfo}. */
+ public static final class Builder {
+ private String mBluetoothAddress;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the bluetooth address of the device, from {@link
+ * android.bluetooth.BluetoothDevice#getAddress()}.
+ *
+ * @param bluetoothAddress The bluetooth address.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setBluetoothAddress(@NonNull String bluetoothAddress) {
+ mBluetoothAddress = bluetoothAddress;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link DeviceInfo} object.
+ *
+ * @return Returns the built {@link DeviceInfo} object.
+ */
+ @NonNull
+ public DeviceInfo build() {
+ return new DeviceInfo(mBluetoothAddress, mExtras);
+ }
+ }
+
+ /**
+ * Gets the bluetooth address of the device.
+ *
+ * @return The bluetooth address from {@link android.bluetooth.BluetoothDevice#getAddress()}.
+ */
+ @NonNull
+ public String getBluetoothAddress() {
+ return mBluetoothAddress;
+ }
+
+ /**
+ * Gets the extras bundle.
+ *
+ * @return The extra bundle.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl
new file mode 100644
index 000000000000..043cae3a576a
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSetting; \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java
new file mode 100644
index 000000000000..dc219a9dfcf4
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSetting.java
@@ -0,0 +1,160 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a device setting item in bluetooth device details page. */
+public final class DeviceSetting implements Parcelable {
+ @DeviceSettingId private final int mSettingId;
+ private final DeviceSettingPreference mPreference;
+ private final Bundle mExtras;
+
+ DeviceSetting(
+ int settingId, @NonNull DeviceSettingPreference preference, @NonNull Bundle extras) {
+ validate(preference);
+ mSettingId = settingId;
+ mPreference = preference;
+ mExtras = extras;
+ }
+
+ private static void validate(DeviceSettingPreference preference) {
+ if (Objects.isNull(preference)) {
+ throw new IllegalArgumentException("Preference must be set");
+ }
+ }
+
+ /** Read a {@link DeviceSetting} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceSetting readFromParcel(@NonNull Parcel in) {
+ int settingId = in.readInt();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ DeviceSettingPreference settingPreference = DeviceSettingPreference.readFromParcel(in);
+ return new DeviceSetting(settingId, settingPreference, extras);
+ }
+
+ public static final Creator<DeviceSetting> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceSetting createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceSetting[] newArray(int size) {
+ return new DeviceSetting[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingId);
+ dest.writeBundle(mExtras);
+ mPreference.writeToParcel(dest, flags);
+ }
+
+ /** Builder class for {@link DeviceSetting}. */
+ public static final class Builder {
+ private int mSettingId;
+ private DeviceSettingPreference mPreference;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSettingId(@DeviceSettingId int settingId) {
+ mSettingId = settingId;
+ return this;
+ }
+
+ /**
+ * Sets the setting preference.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setPreference(@NonNull DeviceSettingPreference settingPreference) {
+ mPreference = settingPreference;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Build the object. */
+ @NonNull
+ public DeviceSetting build() {
+ return new DeviceSetting(mSettingId, mPreference, mExtras);
+ }
+ }
+
+ /**
+ * Gets the setting ID as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the setting ID.
+ */
+ @DeviceSettingId
+ public int getSettingId() {
+ return mSettingId;
+ }
+
+ /**
+ * Gets the setting preference.
+ *
+ * @return Returns the setting preference.
+ */
+ @NonNull
+ public DeviceSettingPreference getPreference() {
+ return mPreference;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
new file mode 100644
index 000000000000..20a0339b9182
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -0,0 +1,113 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+@IntDef(
+ value = {
+ DeviceSettingId.DEVICE_SETTING_ID_UNKNOWN,
+ DeviceSettingId.DEVICE_SETTING_ID_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_ADVANCED_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_LE_AUDIO_HEADER,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_AID_PAIR_OTHER_BUTTON,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_AID_SPACE_LAYOUT,
+ DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_STYLUS,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_EXTRA_CONTROL,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_DEVICE_SLICE_CATEGORY,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_COMPANION_APPS,
+ DeviceSettingId.DEVICE_SETTING_ID_HEARING_DEVICE_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_SPATIAL_AUDIO_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_EXTRA_OPTIONS,
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_RELATED_TOOLS,
+ DeviceSettingId.DEVICE_SETTING_ID_DATA_SYNC_GROUP,
+ DeviceSettingId.DEVICE_SETTING_ID_KEYBOARD_SETTINGS,
+ DeviceSettingId.DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER,
+ DeviceSettingId.DEVICE_SETTING_ID_ANC,
+ },
+ open = true)
+public @interface DeviceSettingId {
+ /** Device setting ID is unknown. */
+ int DEVICE_SETTING_ID_UNKNOWN = 0;
+
+ /** Device setting ID for header. */
+ int DEVICE_SETTING_ID_HEADER = 1;
+
+ /** Device setting ID for advanced header. */
+ int DEVICE_SETTING_ID_ADVANCED_HEADER = 2;
+
+ /** Device setting ID for LeAudio header. */
+ int DEVICE_SETTING_ID_LE_AUDIO_HEADER = 3;
+
+ /** Device setting ID for hearing aid “pair other” button. */
+ int DEVICE_SETTING_ID_HEARING_AID_PAIR_OTHER_BUTTON = 4;
+
+ /** Device setting ID for hearing aid space layout. */
+ int DEVICE_SETTING_ID_HEARING_AID_SPACE_LAYOUT = 5;
+
+ /** Device setting ID for action buttons(Forget, Connect/Disconnect). */
+ int DEVICE_SETTING_ID_ACTION_BUTTONS = 6;
+
+ /** Device setting ID for stylus device. */
+ int DEVICE_SETTING_ID_DEVICE_STYLUS = 7;
+
+ /** Device setting ID for bluetooth extra control. */
+ int DEVICE_SETTING_ID_BLUETOOTH_EXTRA_CONTROL = 8;
+
+ /** Device setting ID for bluetooth device slice category. */
+ int DEVICE_SETTING_ID_BLUETOOTH_DEVICE_SLICE_CATEGORY = 9;
+
+ /** Device setting ID for device companion apps. */
+ int DEVICE_SETTING_ID_DEVICE_COMPANION_APPS = 10;
+
+ /** Device setting ID for hearing device group. */
+ int DEVICE_SETTING_ID_HEARING_DEVICE_GROUP = 11;
+
+ /** Device setting ID for bluetooth audio device type group. */
+ int DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP = 12;
+
+ /** Device setting ID for spatial audio group. */
+ int DEVICE_SETTING_ID_SPATIAL_AUDIO_GROUP = 13;
+
+ /** Device setting ID for bluetooth profiles. */
+ int DEVICE_SETTING_ID_BLUETOOTH_PROFILES = 14;
+
+ /** Device setting ID for bluetooth extra options. */
+ int DEVICE_SETTING_ID_BLUETOOTH_EXTRA_OPTIONS = 15;
+
+ /** Device setting ID for bluetooth related tools. */
+ int DEVICE_SETTING_ID_BLUETOOTH_RELATED_TOOLS = 16;
+
+ /** Device setting ID for data sync group. */
+ int DEVICE_SETTING_ID_DATA_SYNC_GROUP = 17;
+
+ /** Device setting ID for keyboard settings. */
+ int DEVICE_SETTING_ID_KEYBOARD_SETTINGS = 18;
+
+ /** Device setting ID for device details footer. */
+ int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
+
+ /** Device setting ID for ANC. */
+ int DEVICE_SETTING_ID_ANC = 1001;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
new file mode 100644
index 000000000000..9ee33b0dbffe
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a device settings item in bluetooth device details config.
+ *
+ * @property settingId The setting ID of the item, as defined by IntDef [DeviceSettingId].
+ * @property packageName The package name for service binding.
+ * @property className The class name for service binding.
+ * @property intentAction The intent action for service binding.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingItem(
+ @DeviceSettingId val settingId: Int,
+ val packageName: String,
+ val className: String,
+ val intentAction: String,
+ val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.run {
+ writeInt(settingId)
+ writeString(packageName)
+ writeString(className)
+ writeString(intentAction)
+ writeBundle(extras)
+ }
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<DeviceSettingItem> =
+ object : Parcelable.Creator<DeviceSettingItem> {
+ override fun createFromParcel(parcel: Parcel) =
+ parcel.run {
+ DeviceSettingItem(
+ settingId = readInt(),
+ packageName = readString() ?: "",
+ className = readString() ?: "",
+ intentAction = readString() ?: "",
+ extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
+ )
+ }
+
+ override fun newArray(size: Int): Array<DeviceSettingItem?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
new file mode 100644
index 000000000000..790939a18a0c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreference.java
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+/** An abstract class representing a device setting preference. */
+public abstract class DeviceSettingPreference {
+ @DeviceSettingType private final int mSettingType;
+
+ public static final DeviceSettingPreference UNKNOWN =
+ new DeviceSettingPreference(DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN) {};
+
+ protected DeviceSettingPreference(@DeviceSettingType int settingType) {
+ mSettingType = settingType;
+ }
+
+ /** Read a {@link DeviceSettingPreference} instance from {@link Parcel} */
+ @NonNull
+ public static DeviceSettingPreference readFromParcel(@NonNull Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH:
+ return ActionSwitchPreference.readFromParcel(in);
+ case DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE:
+ return MultiTogglePreference.readFromParcel(in);
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ /** Writes the instance to {@link Parcel}. */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingType);
+ }
+
+ /**
+ * Gets the setting type, as defined by IntDef {@link DeviceSettingType}.
+ *
+ * @return the setting type.
+ */
+ @DeviceSettingType
+ public int getSettingType() {
+ return mSettingType;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java
new file mode 100644
index 000000000000..a982af709f69
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPreferenceState.java
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Parcel;
+
+import androidx.annotation.NonNull;
+
+/** An abstract class representing a device setting preference state. */
+public abstract class DeviceSettingPreferenceState {
+ @DeviceSettingType private final int mSettingType;
+
+ public static final DeviceSettingPreferenceState UNKNOWN =
+ new DeviceSettingPreferenceState(DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN) {};
+
+ protected DeviceSettingPreferenceState(@DeviceSettingType int settingType) {
+ mSettingType = settingType;
+ }
+
+ /** Reads a {@link DeviceSettingPreferenceState} from {@link Parcel}. */
+ @NonNull
+ public static DeviceSettingPreferenceState readFromParcel(@NonNull Parcel in) {
+ int type = in.readInt();
+ switch (type) {
+ case DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH:
+ return ActionSwitchPreferenceState.readFromParcel(in);
+ case DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE:
+ return MultiTogglePreferenceState.readFromParcel(in);
+ default:
+ return UNKNOWN;
+ }
+ }
+
+ /** Writes the object to parcel. */
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingType);
+ }
+
+ /**
+ * Gets the setting type, as defined by IntDef {@link DeviceSettingType}.
+ *
+ * @return The setting type.
+ */
+ @DeviceSettingType
+ public int getSettingType() {
+ return mSettingType;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl
new file mode 100644
index 000000000000..61429a63128f
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSettingState; \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
new file mode 100644
index 000000000000..63fd4eb425c0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingState.java
@@ -0,0 +1,165 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a device setting state. */
+public class DeviceSettingState implements Parcelable {
+ @DeviceSettingId private final int mSettingId;
+ private final DeviceSettingPreferenceState mPreferenceState;
+ private final Bundle mExtras;
+
+ DeviceSettingState(
+ @DeviceSettingId int settingId,
+ @NonNull DeviceSettingPreferenceState preferenceState,
+ @NonNull Bundle extras) {
+ validate(preferenceState);
+ mSettingId = settingId;
+ mPreferenceState = preferenceState;
+ mExtras = extras;
+ }
+
+ private static void validate(DeviceSettingPreferenceState preferenceState) {
+ if (Objects.isNull(preferenceState)) {
+ throw new IllegalArgumentException("PreferenceState must be set");
+ }
+ }
+
+ /** Reads a {@link DeviceSettingState} from {@link Parcel}. */
+ @NonNull
+ public static DeviceSettingState readFromParcel(@NonNull Parcel in) {
+ int settingId = in.readInt();
+ Bundle extra = in.readBundle(Bundle.class.getClassLoader());
+ DeviceSettingPreferenceState preferenceState =
+ DeviceSettingPreferenceState.readFromParcel(in);
+ return new DeviceSettingState(settingId, preferenceState, extra);
+ }
+
+ public static final Creator<DeviceSettingState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public DeviceSettingState createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public DeviceSettingState[] newArray(int size) {
+ return new DeviceSettingState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Writes the instance to {@link Parcel}. */
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSettingId);
+ dest.writeBundle(mExtras);
+ mPreferenceState.writeToParcel(dest, flags);
+ }
+
+ /** Builder class for {@link DeviceSettingState}. */
+ public static final class Builder {
+ private int mSettingId;
+ private DeviceSettingPreferenceState mSettingPreferenceState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setSettingId(@DeviceSettingId int settingId) {
+ mSettingId = settingId;
+ return this;
+ }
+
+ /**
+ * Sets the setting preference state.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setPreferenceState(
+ @NonNull DeviceSettingPreferenceState settingPreferenceState) {
+ mSettingPreferenceState = settingPreferenceState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Build the object. */
+ @NonNull
+ public DeviceSettingState build() {
+ return new DeviceSettingState(mSettingId, mSettingPreferenceState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the setting ID, as defined by IntDef {@link DeviceSettingId}.
+ *
+ * @return the setting ID.
+ */
+ @DeviceSettingId
+ public int getSettingId() {
+ return mSettingId;
+ }
+
+ /**
+ * Gets the preference state of the setting.
+ *
+ * @return the setting preference state.
+ */
+ @NonNull
+ public DeviceSettingPreferenceState getPreferenceState() {
+ return mPreferenceState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
new file mode 100644
index 000000000000..ee4d90fe4171
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingType.java
@@ -0,0 +1,41 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+@IntDef(
+ value = {
+ DeviceSettingType.DEVICE_SETTING_TYPE_UNKNOWN,
+ DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH,
+ DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE,
+ },
+ open = true)
+public @interface DeviceSettingType {
+ /** Device setting type is unknown. */
+ int DEVICE_SETTING_TYPE_UNKNOWN = 0;
+
+ /** Device setting type is action/switch preference. */
+ int DEVICE_SETTING_TYPE_ACTION_SWITCH = 1;
+
+ /** Device setting type is multi-toggle preference. */
+ int DEVICE_SETTING_TYPE_MULTI_TOGGLE = 2;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl
new file mode 100644
index 000000000000..3201d13cd92e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+parcelable DeviceSettingsConfig; \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
new file mode 100644
index 000000000000..c8a2e9ca3af1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfig.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
+
+/**
+ * A data class representing a bluetooth device details config.
+ *
+ * @property mainContentItems The setting items to be shown in main page.
+ * @property moreSettingsItems The setting items to be shown in more settings page.
+ * @property moreSettingsFooter The footer in more settings page.
+ * @property extras Extra bundle
+ */
+data class DeviceSettingsConfig(
+ val mainContentItems: List<DeviceSettingItem>,
+ val moreSettingsItems: List<DeviceSettingItem>,
+ val moreSettingsFooter: String,
+ val extras: Bundle = Bundle.EMPTY,
+) : Parcelable {
+
+ override fun describeContents(): Int = 0
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.run {
+ writeTypedList(mainContentItems)
+ writeTypedList(moreSettingsItems)
+ writeString(moreSettingsFooter)
+ writeBundle(extras)
+ }
+ }
+
+ companion object {
+ @JvmField
+ val CREATOR: Parcelable.Creator<DeviceSettingsConfig> =
+ object : Parcelable.Creator<DeviceSettingsConfig> {
+ override fun createFromParcel(parcel: Parcel): DeviceSettingsConfig =
+ parcel.run {
+ DeviceSettingsConfig(
+ mainContentItems =
+ arrayListOf<DeviceSettingItem>().also {
+ readTypedList(it, DeviceSettingItem.CREATOR)
+ },
+ moreSettingsItems =
+ arrayListOf<DeviceSettingItem>().also {
+ readTypedList(it, DeviceSettingItem.CREATOR)
+ },
+ moreSettingsFooter = readString()!!,
+ extras = readBundle((Bundle::class.java.classLoader))!!,
+ )
+ }
+
+ override fun newArray(size: Int): Array<DeviceSettingsConfig?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl
new file mode 100644
index 000000000000..385a780b01f3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsListener.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceSetting;
+
+interface IDeviceSettingsListener {
+ oneway void onDeviceSettingsChanged(in List<DeviceSetting> settings) = 0;
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
new file mode 100644
index 000000000000..d5efac9d0336
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/IDeviceSettingsProviderService.aidl
@@ -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.
+ */
+
+package com.android.settingslib.bluetooth.devicesettings;
+
+import com.android.settingslib.bluetooth.devicesettings.DeviceInfo;
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingState;
+import com.android.settingslib.bluetooth.devicesettings.IDeviceSettingsListener;
+
+oneway interface IDeviceSettingsProviderService {
+ void registerDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+ void unregisterDeviceSettingsListener(in DeviceInfo device, in IDeviceSettingsListener callback);
+ void updateDeviceSettings(in DeviceInfo device, in DeviceSettingState params);
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
new file mode 100644
index 000000000000..01bb6f013d16
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreference.java
@@ -0,0 +1,232 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/** A data class representing a multi-toggle preference. */
+public class MultiTogglePreference extends DeviceSettingPreference implements Parcelable {
+ private final String mTitle;
+ private final ImmutableList<ToggleInfo> mToggleInfos;
+ private final int mState;
+ private final boolean mIsAllowedChangingState;
+ private final Bundle mExtras;
+
+ MultiTogglePreference(
+ @NonNull String title,
+ List<ToggleInfo> toggleInfos,
+ int state,
+ boolean allowChangingState,
+ Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ validate(title, state);
+ mTitle = title;
+ mToggleInfos = ImmutableList.copyOf(toggleInfos);
+ mState = state;
+ mIsAllowedChangingState = allowChangingState;
+ mExtras = extras;
+ }
+
+ private static void validate(String title, int state) {
+ if (Objects.isNull(title)) {
+ throw new IllegalArgumentException("Title must be set");
+ }
+ if (state < 0) {
+ throw new IllegalArgumentException("State must be a non-negative integer");
+ }
+ }
+
+ /** Read a {@link MultiTogglePreference} from {@link Parcel}. */
+ @NonNull
+ public static MultiTogglePreference readFromParcel(@NonNull Parcel in) {
+ String title = in.readString();
+ List<ToggleInfo> toggleInfos = new ArrayList<>();
+ in.readTypedList(toggleInfos, ToggleInfo.CREATOR);
+ int state = in.readInt();
+ boolean allowChangingState = in.readBoolean();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new MultiTogglePreference(title, toggleInfos, state, allowChangingState, extras);
+ }
+
+ public static final Creator<MultiTogglePreference> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public MultiTogglePreference createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public MultiTogglePreference[] newArray(int size) {
+ return new MultiTogglePreference[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mTitle);
+ dest.writeTypedList(mToggleInfos, flags);
+ dest.writeInt(mState);
+ dest.writeBoolean(mIsAllowedChangingState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link MultiTogglePreference}. */
+ public static final class Builder {
+ private String mTitle;
+ private ImmutableList.Builder<ToggleInfo> mToggleInfos = new ImmutableList.Builder<>();
+ private int mState;
+ private boolean mAllowChangingState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the title of the preference.
+ *
+ * @param title The title of the preference.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setTitle(@NonNull String title) {
+ mTitle = title;
+ return this;
+ }
+
+ /**
+ * Adds a toggle in the preference.
+ *
+ * @param toggleInfo The toggle to add.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder addToggleInfo(@NonNull ToggleInfo toggleInfo) {
+ mToggleInfos.add(toggleInfo);
+ return this;
+ }
+
+ /**
+ * Sets the state of the preference.
+ *
+ * @param state The index of the enabled toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ /**
+ * Sets whether state can be changed by user.
+ *
+ * @param allowChangingState Whether user is allowed to change state.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setAllowChangingState(boolean allowChangingState) {
+ mAllowChangingState = allowChangingState;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ToggleInfo} object.
+ *
+ * @return Returns the built {@link ToggleInfo} object.
+ */
+ @NonNull
+ public MultiTogglePreference build() {
+ return new MultiTogglePreference(
+ mTitle, mToggleInfos.build(), mState, mAllowChangingState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the title of the preference.
+ *
+ * @return The title.
+ */
+ @NonNull
+ public String getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Gets the state of the {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of the enabled toggle.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Gets the toggle list in the preference.
+ *
+ * @return the toggle list.
+ */
+ @NonNull
+ public List<ToggleInfo> getToggleInfos() {
+ return mToggleInfos;
+ }
+
+ /**
+ * Gets whether the state can be changed by user.
+ *
+ * @return Whether the state can be changed by user.
+ */
+ public boolean isAllowedChangingState() {
+ return mIsAllowedChangingState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
new file mode 100644
index 000000000000..239df0b55b22
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceState.java
@@ -0,0 +1,126 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+/** A data class representing a multi-toggle preference state. */
+public class MultiTogglePreferenceState extends DeviceSettingPreferenceState implements Parcelable {
+ private final int mState;
+ private final Bundle mExtras;
+
+ MultiTogglePreferenceState(int state, @NonNull Bundle extras) {
+ super(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ mState = state;
+ mExtras = extras;
+ }
+
+ /** Reads a {@link MultiTogglePreferenceState} from {@link Parcel}. */
+ @NonNull
+ public static MultiTogglePreferenceState readFromParcel(@NonNull Parcel in) {
+ int state = in.readInt();
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new MultiTogglePreferenceState(state, extras);
+ }
+
+ public static final Creator<MultiTogglePreferenceState> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public MultiTogglePreferenceState createFromParcel(@NonNull Parcel in) {
+ in.readInt();
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public MultiTogglePreferenceState[] newArray(int size) {
+ return new MultiTogglePreferenceState[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mState);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link MultiTogglePreferenceState}. */
+ public static final class Builder {
+ private int mState;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder() {}
+
+ /**
+ * Sets the state of {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of enabled toggle.
+ */
+ @NonNull
+ public Builder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /** Builds the object. */
+ @NonNull
+ public MultiTogglePreferenceState build() {
+ return new MultiTogglePreferenceState(mState, mExtras);
+ }
+ }
+
+ /**
+ * Gets the state of the {@link MultiTogglePreference}.
+ *
+ * @return Returns the index of the enabled toggle.
+ */
+ public int getState() {
+ return mState;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java
new file mode 100644
index 000000000000..7dcf3aa7239e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfo.java
@@ -0,0 +1,167 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/** A data class representing a toggle in {@link MultiTogglePreference}. */
+public class ToggleInfo implements Parcelable {
+ private final String mLabel;
+ private final Bitmap mIcon;
+ private final Bundle mExtras;
+
+ ToggleInfo(@NonNull String label, @NonNull Bitmap icon, @NonNull Bundle extras) {
+ validate(label, icon);
+ mLabel = label;
+ mIcon = icon;
+ mExtras = extras;
+ }
+
+ private static void validate(String label, Bitmap icon) {
+ if (Objects.isNull(label)) {
+ throw new IllegalArgumentException("Label must be set");
+ }
+ if (Objects.isNull(icon)) {
+ throw new IllegalArgumentException("Icon must be set");
+ }
+ }
+
+ /** Read a {@link ToggleInfo} instance from {@link Parcel}. */
+ @NonNull
+ public static ToggleInfo readFromParcel(@NonNull Parcel in) {
+ String label = in.readString();
+ Bitmap icon = in.readParcelable(Bitmap.class.getClassLoader());
+ Bundle extras = in.readBundle(Bundle.class.getClassLoader());
+ return new ToggleInfo(label, icon, extras);
+ }
+
+ public static final Creator<ToggleInfo> CREATOR =
+ new Creator<>() {
+ @Override
+ @NonNull
+ public ToggleInfo createFromParcel(@NonNull Parcel in) {
+ return readFromParcel(in);
+ }
+
+ @Override
+ @NonNull
+ public ToggleInfo[] newArray(int size) {
+ return new ToggleInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mLabel);
+ dest.writeParcelable(mIcon, flags);
+ dest.writeBundle(mExtras);
+ }
+
+ /** Builder class for {@link ToggleInfo}. */
+ public static final class Builder {
+ private Bitmap mIcon;
+ private String mLabel;
+ private Bundle mExtras = Bundle.EMPTY;
+
+ /**
+ * Sets the label of the toggle.
+ *
+ * @param label The label of the toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setLabel(@NonNull String label) {
+ mLabel = label;
+ return this;
+ }
+
+ /**
+ * Sets the icon of the toggle.
+ *
+ * @param icon The icon of the toggle.
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setIcon(@NonNull Bitmap icon) {
+ mIcon = icon;
+ return this;
+ }
+
+ /**
+ * Sets the extras bundle.
+ *
+ * @return Returns the Builder object.
+ */
+ @NonNull
+ public Builder setExtras(@NonNull Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link ToggleInfo} object.
+ *
+ * @return Returns the built {@link ToggleInfo} object.
+ */
+ @NonNull
+ public ToggleInfo build() {
+ return new ToggleInfo(mLabel, mIcon, mExtras);
+ }
+ }
+
+ /**
+ * Gets the label of the toggle.
+ *
+ * @return the label to be shown under the toggle
+ */
+ @NonNull
+ public String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * Gets the icon of the toggle.
+ *
+ * @return the icon in toggle
+ */
+ @NonNull
+ public Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * Gets the extras Bundle.
+ *
+ * @return Returns a Bundle object.
+ */
+ @NonNull
+ public Bundle getExtras() {
+ return mExtras;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java
new file mode 100644
index 000000000000..e8e1556b8fe7
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceStateTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ActionSwitchPreferenceStateTest {
+
+ @Test
+ public void getMethods() {
+ ActionSwitchPreferenceState state1 =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ ActionSwitchPreferenceState state2 =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(false)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ assertThat(state1.getChecked()).isTrue();
+ assertThat(state2.getChecked()).isFalse();
+ assertThat(state1.getExtras().getString("key1")).isEqualTo("value1");
+ assertThat(state2.getExtras().getString("key2")).isEqualTo("value2");
+ }
+
+ @Test
+ public void parcelOperation_notChecked() {
+ ActionSwitchPreferenceState state =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(false)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ ActionSwitchPreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getChecked()).isEqualTo(state.getChecked());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_checked() {
+ ActionSwitchPreferenceState state =
+ new ActionSwitchPreferenceState.Builder()
+ .setChecked(true)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ ActionSwitchPreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getChecked()).isEqualTo(state.getChecked());
+ assertThat(fromParcel.getExtras().getString("key2"))
+ .isEqualTo(state.getExtras().getString("key2"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ActionSwitchPreferenceState writeAndRead(ActionSwitchPreferenceState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ActionSwitchPreferenceState fromParcel =
+ ActionSwitchPreferenceState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java
new file mode 100644
index 000000000000..354d0f658544
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ActionSwitchPreferenceTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ActionSwitchPreferenceTest {
+
+ @Test
+ public void build_withoutTitle_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder().setSummary("summary").build();
+ });
+ }
+
+ @Test
+ public void build_withTitle_successfully() {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder().setTitle("title").build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ ActionSwitchPreference unused =
+ new ActionSwitchPreference.Builder()
+ .setTitle("title")
+ .setSummary("summary")
+ .setIntent(new Intent("intent_action"))
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setHasSwitch(true)
+ .setChecked(true)
+ .setAllowedChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ Intent intent = new Intent("intent_action");
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(intent).build();
+
+ assertThat(preference.getTitle()).isEqualTo("title");
+ assertThat(preference.getSummary()).isEqualTo("summary");
+ assertThat(preference.getIcon()).isSameInstanceAs(icon);
+ assertThat(preference.getIntent()).isSameInstanceAs(intent);
+ assertThat(preference.hasSwitch()).isTrue();
+ assertThat(preference.getChecked()).isTrue();
+ assertThat(preference.isAllowedChangingState()).isTrue();
+ assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ Intent intent = new Intent("intent_action");
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(intent).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon().sameAs(preference.getIcon())).isTrue();
+ assertThat(fromParcel.getIntent().getAction()).isSameInstanceAs("intent_action");
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_noIntent() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ActionSwitchPreference preference = builder().setIcon(icon).setIntent(null).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon().sameAs(preference.getIcon())).isTrue();
+ assertThat(preference.getIntent()).isNull();
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_noIcon() {
+ Intent intent = new Intent("intent_action");
+ ActionSwitchPreference preference = builder().setIcon(null).setIntent(intent).build();
+
+ ActionSwitchPreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getSummary()).isEqualTo(preference.getSummary());
+ assertThat(fromParcel.getIcon()).isNull();
+ assertThat(fromParcel.getIntent().getAction()).isSameInstanceAs("intent_action");
+ assertThat(fromParcel.hasSwitch()).isEqualTo(preference.hasSwitch());
+ assertThat(fromParcel.getChecked()).isEqualTo(preference.getChecked());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ActionSwitchPreference writeAndRead(ActionSwitchPreference preference) {
+ Parcel parcel = Parcel.obtain();
+ preference.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ActionSwitchPreference fromParcel = ActionSwitchPreference.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+
+ private ActionSwitchPreference.Builder builder() {
+ return new ActionSwitchPreference.Builder()
+ .setTitle("title")
+ .setSummary("summary")
+ .setHasSwitch(true)
+ .setChecked(true)
+ .setAllowedChangingState(true)
+ .setExtras(buildBundle("key1", "value1"));
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java
new file mode 100644
index 000000000000..fd5b07508b52
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceInfoTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceInfoTest {
+ @Test
+ public void build_withoutBluetoothAddress_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceInfo unused =
+ new DeviceInfo.Builder()
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceInfo unused = new DeviceInfo.Builder().setBluetoothAddress("12:34:56:78").build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceInfo unused =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ DeviceInfo info =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(info.getBluetoothAddress()).isEqualTo("12:34:56:78");
+ assertThat(info.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ DeviceInfo info =
+ new DeviceInfo.Builder()
+ .setBluetoothAddress("12:34:56:78")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceInfo fromParcel = writeAndRead(info);
+
+ assertThat(fromParcel.getBluetoothAddress()).isEqualTo(info.getBluetoothAddress());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(info.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceInfo writeAndRead(DeviceInfo state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceInfo fromParcel = DeviceInfo.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
new file mode 100644
index 000000000000..56e9b6c27925
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItemTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingItemTest {
+
+ @Test
+ fun parcelOperation() {
+ val item =
+ DeviceSettingItem(
+ settingId = 1,
+ packageName = "package_name",
+ className = "class_name",
+ intentAction = "intent_action",
+ extras = Bundle().apply { putString("key1", "value1") },
+ )
+
+ val fromParcel = writeAndRead(item)
+
+ assertThat(fromParcel.settingId).isEqualTo(item.settingId)
+ assertThat(fromParcel.packageName).isEqualTo(item.packageName)
+ assertThat(fromParcel.className).isEqualTo(item.className)
+ assertThat(fromParcel.intentAction).isEqualTo(item.intentAction)
+ assertThat(fromParcel.extras.getString("key1")).isEqualTo(item.extras.getString("key1"))
+ }
+
+ private fun writeAndRead(item: DeviceSettingItem): DeviceSettingItem {
+ val parcel = Parcel.obtain()
+ item.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return DeviceSettingItem.CREATOR.createFromParcel(parcel)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java
new file mode 100644
index 000000000000..12b7a0f0167c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingStateTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingStateTest {
+ private static final ActionSwitchPreferenceState ACTION_SWITCH_PREFERENCE_STATE =
+ new ActionSwitchPreferenceState.Builder().setChecked(true).build();
+ private static final MultiTogglePreferenceState MULTI_TOGGLE_PREFERENCE_STATE =
+ new MultiTogglePreferenceState.Builder().setState(123).build();
+
+ @Test
+ public void build_withoutPreferenceState_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceSettingState unused =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods_actionSwitchPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(state.getSettingId()).isEqualTo(123);
+ assertThat(state.getPreferenceState()).isInstanceOf(ActionSwitchPreferenceState.class);
+ assertThat(state.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void getMethods_multiTogglePreference() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(MULTI_TOGGLE_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(state.getSettingId()).isEqualTo(123);
+ assertThat(state.getPreferenceState()).isInstanceOf(MultiTogglePreferenceState.class);
+ assertThat(state.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation_actionSwitchPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(ACTION_SWITCH_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState()).isInstanceOf(ActionSwitchPreferenceState.class);
+ assertThat(fromParcel.getPreferenceState().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ assertThat(((ActionSwitchPreferenceState) fromParcel.getPreferenceState()).getChecked())
+ .isTrue();
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_multiTogglePreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(MULTI_TOGGLE_PREFERENCE_STATE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState()).isInstanceOf(MultiTogglePreferenceState.class);
+ assertThat(fromParcel.getPreferenceState().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ assertThat(((MultiTogglePreferenceState) fromParcel.getPreferenceState()).getState())
+ .isEqualTo(123);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_unknownPreferenceState() {
+ DeviceSettingState state =
+ new DeviceSettingState.Builder()
+ .setSettingId(123)
+ .setPreferenceState(new DeviceSettingPreferenceState(123) {})
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSettingState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(state.getSettingId());
+ assertThat(fromParcel.getPreferenceState())
+ .isSameInstanceAs(DeviceSettingPreferenceState.UNKNOWN);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceSettingState writeAndRead(DeviceSettingState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceSettingState fromParcel = DeviceSettingState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java
new file mode 100644
index 000000000000..98dc54b2e33d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class DeviceSettingTest {
+ private static final ActionSwitchPreference ACTION_SWITCH_PREFERENCE =
+ new ActionSwitchPreference.Builder().setTitle("action_switch_preference").build();
+ private static final MultiTogglePreference MULTI_TOGGLE_PREFERENCE =
+ new MultiTogglePreference.Builder().setTitle("multi_toggle_preference").build();
+
+ @Test
+ public void build_withoutPreference_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ DeviceSetting unused =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods_actionSwitchPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(setting.getSettingId()).isEqualTo(123);
+ assertThat(setting.getPreference()).isInstanceOf(ActionSwitchPreference.class);
+ assertThat(setting.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void getMethods_multiTogglePreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(MULTI_TOGGLE_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(setting.getSettingId()).isEqualTo(123);
+ assertThat(setting.getPreference()).isInstanceOf(MultiTogglePreference.class);
+ assertThat(setting.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation_actionSwitchPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(ACTION_SWITCH_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isInstanceOf(ActionSwitchPreference.class);
+ assertThat(fromParcel.getPreference().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_ACTION_SWITCH);
+ assertThat(((ActionSwitchPreference) fromParcel.getPreference()).getTitle())
+ .isEqualTo("action_switch_preference");
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_multiTogglePreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(MULTI_TOGGLE_PREFERENCE)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isInstanceOf(MultiTogglePreference.class);
+ assertThat(fromParcel.getPreference().getSettingType())
+ .isEqualTo(DeviceSettingType.DEVICE_SETTING_TYPE_MULTI_TOGGLE);
+ assertThat(((MultiTogglePreference) fromParcel.getPreference()).getTitle())
+ .isEqualTo("multi_toggle_preference");
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ @Test
+ public void parcelOperation_unknownPreference() {
+ DeviceSetting setting =
+ new DeviceSetting.Builder()
+ .setSettingId(123)
+ .setPreference(new DeviceSettingPreference(123) {})
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ DeviceSetting fromParcel = writeAndRead(setting);
+
+ assertThat(fromParcel.getSettingId()).isEqualTo(setting.getSettingId());
+ assertThat(fromParcel.getPreference()).isSameInstanceAs(DeviceSettingPreference.UNKNOWN);
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(setting.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private DeviceSetting writeAndRead(DeviceSetting state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DeviceSetting fromParcel = DeviceSetting.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
new file mode 100644
index 000000000000..2b29a6ef5e5a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -0,0 +1,84 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings
+
+import android.os.Bundle
+import android.os.Parcel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class DeviceSettingsConfigTest {
+
+ @Test
+ fun parcelOperation() {
+ val config =
+ DeviceSettingsConfig(
+ mainContentItems =
+ listOf(
+ DeviceSettingItem(
+ 1,
+ "package_name_1",
+ "class_name_1",
+ "intent_action_1",
+ Bundle()
+ )
+ ),
+ moreSettingsItems =
+ listOf(
+ DeviceSettingItem(
+ 2,
+ "package_name_2",
+ "class_name_2",
+ "intent_action_2",
+ Bundle()
+ )
+ ),
+ moreSettingsFooter = "footer",
+ extras = Bundle().apply { putString("key1", "value1") },
+ )
+
+ val fromParcel = writeAndRead(config)
+
+ assertThat(fromParcel.mainContentItems.stream().map { it.settingId }.toList())
+ .containsExactly(1)
+ assertThat(fromParcel.mainContentItems.stream().map { it.packageName }.toList())
+ .containsExactly("package_name_1")
+ assertThat(fromParcel.mainContentItems.stream().map { it.className }.toList())
+ .containsExactly("class_name_1")
+ assertThat(fromParcel.mainContentItems.stream().map { it.intentAction }.toList())
+ .containsExactly("intent_action_1")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.settingId }.toList())
+ .containsExactly(2)
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.packageName }.toList())
+ .containsExactly("package_name_2")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.className }.toList())
+ .containsExactly("class_name_2")
+ assertThat(fromParcel.moreSettingsItems.stream().map { it.intentAction }.toList())
+ .containsExactly("intent_action_2")
+ assertThat(fromParcel.moreSettingsFooter).isEqualTo(config.moreSettingsFooter)
+ }
+
+ private fun writeAndRead(item: DeviceSettingsConfig): DeviceSettingsConfig {
+ val parcel = Parcel.obtain()
+ item.writeToParcel(parcel, 0)
+ parcel.setDataPosition(0)
+ return DeviceSettingsConfig.CREATOR.createFromParcel(parcel)
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java
new file mode 100644
index 000000000000..2645fc5f16ff
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceStateTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MultiTogglePreferenceStateTest {
+
+ @Test
+ public void getMethods() {
+ MultiTogglePreferenceState state1 =
+ new MultiTogglePreferenceState.Builder()
+ .setState(1)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ MultiTogglePreferenceState state2 =
+ new MultiTogglePreferenceState.Builder()
+ .setState(2)
+ .setExtras(buildBundle("key2", "value2"))
+ .build();
+
+ assertThat(state1.getState()).isEqualTo(1);
+ assertThat(state2.getState()).isEqualTo(2);
+ assertThat(state1.getExtras().getString("key1")).isEqualTo("value1");
+ assertThat(state2.getExtras().getString("key2")).isEqualTo("value2");
+ }
+
+ @Test
+ public void parcelOperation() {
+ MultiTogglePreferenceState state =
+ new MultiTogglePreferenceState.Builder()
+ .setState(123)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ MultiTogglePreferenceState fromParcel = writeAndRead(state);
+
+ assertThat(fromParcel.getState()).isEqualTo(state.getState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(state.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private MultiTogglePreferenceState writeAndRead(MultiTogglePreferenceState state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MultiTogglePreferenceState fromParcel =
+ MultiTogglePreferenceState.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
new file mode 100644
index 000000000000..62fcb5ec3011
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/MultiTogglePreferenceTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class MultiTogglePreferenceTest {
+ private static final Bitmap ICON = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ private static final ToggleInfo TOGGLE_INFO_1 =
+ new ToggleInfo.Builder().setLabel("label1").setIcon(ICON).build();
+ private static final ToggleInfo TOGGLE_INFO_2 =
+ new ToggleInfo.Builder().setLabel("label2").setIcon(ICON).build();
+
+ @Test
+ public void build_withoutTitle_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .addToggleInfo(TOGGLE_INFO_1)
+ .setState(0)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withNegativeState_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .setState(-1)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ MultiTogglePreference unused =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ MultiTogglePreference preference =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(preference.getTitle()).isEqualTo("title");
+ assertThat(preference.getToggleInfos().stream().map(ToggleInfo::getLabel).toList())
+ .containsExactly("label1", "label2");
+ assertThat(preference.getState()).isEqualTo(123);
+ assertThat(preference.isAllowedChangingState()).isTrue();
+ assertThat(preference.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ MultiTogglePreference preference =
+ new MultiTogglePreference.Builder()
+ .setTitle("title")
+ .addToggleInfo(TOGGLE_INFO_1)
+ .addToggleInfo(TOGGLE_INFO_2)
+ .setState(123)
+ .setAllowChangingState(true)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ MultiTogglePreference fromParcel = writeAndRead(preference);
+
+ assertThat(fromParcel.getTitle()).isEqualTo(preference.getTitle());
+ assertThat(fromParcel.getToggleInfos().stream().map(ToggleInfo::getLabel).toList())
+ .containsExactly("label1", "label2");
+ assertThat(fromParcel.getState()).isEqualTo(preference.getState());
+ assertThat(fromParcel.isAllowedChangingState())
+ .isEqualTo(preference.isAllowedChangingState());
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(preference.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private MultiTogglePreference writeAndRead(MultiTogglePreference preference) {
+ Parcel parcel = Parcel.obtain();
+ preference.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MultiTogglePreference fromParcel = MultiTogglePreference.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java
new file mode 100644
index 000000000000..439749a53d32
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/ToggleInfoTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.settingslib.bluetooth.devicesettings;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class ToggleInfoTest {
+
+ @Test
+ public void build_withoutIcon_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutLabel_fail() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ });
+ }
+
+ @Test
+ public void build_withoutExtra_successfully() {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .build();
+ }
+
+ @Test
+ public void build_withAllFields_successfully() {
+ ToggleInfo unused =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+ }
+
+ @Test
+ public void getMethods() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ToggleInfo info =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(icon)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ assertThat(info.getLabel()).isEqualTo("label");
+ assertThat(info.getIcon()).isSameInstanceAs(icon);
+ assertThat(info.getExtras().getString("key1")).isEqualTo("value1");
+ }
+
+ @Test
+ public void parcelOperation() {
+ Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ ToggleInfo info =
+ new ToggleInfo.Builder()
+ .setLabel("label")
+ .setIcon(icon)
+ .setExtras(buildBundle("key1", "value1"))
+ .build();
+
+ ToggleInfo fromParcel = writeAndRead(info);
+
+ assertThat(fromParcel.getLabel()).isEqualTo(info.getLabel());
+ assertThat(fromParcel.getIcon().sameAs(info.getIcon())).isTrue();
+ assertThat(fromParcel.getExtras().getString("key1"))
+ .isEqualTo(info.getExtras().getString("key1"));
+ }
+
+ private Bundle buildBundle(String key, String value) {
+ Bundle bundle = new Bundle();
+ bundle.putString(key, value);
+ return bundle;
+ }
+
+ private ToggleInfo writeAndRead(ToggleInfo state) {
+ Parcel parcel = Parcel.obtain();
+ state.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ToggleInfo fromParcel = ToggleInfo.CREATOR.createFromParcel(parcel);
+ return fromParcel;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c9b35a0ae833..e1447dc8410c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -861,6 +861,23 @@ public class InfoMediaManagerTest {
}
@Test
+ public void addMediaDevice_withAddresslessBluetoothDevice_shouldIgnoreDeviceAndNotCrash() {
+ MediaRoute2Info bluetoothRoute =
+ new MediaRoute2Info.Builder(TEST_BLUETOOTH_ROUTE).setAddress(null).build();
+
+ final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
+ mock(CachedBluetoothDeviceManager.class);
+ when(mLocalBluetoothManager.getCachedDeviceManager())
+ .thenReturn(cachedBluetoothDeviceManager);
+ when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))).thenReturn(null);
+
+ mInfoMediaManager.mMediaDevices.clear();
+ mInfoMediaManager.addMediaDevice(bluetoothRoute, TEST_SYSTEM_ROUTING_SESSION);
+
+ assertThat(mInfoMediaManager.mMediaDevices.size()).isEqualTo(0);
+ }
+
+ @Test
public void onRoutesUpdated_setsFirstSelectedRouteAsCurrentConnectedDevice() {
final CachedBluetoothDeviceManager cachedBluetoothDeviceManager =
mock(CachedBluetoothDeviceManager.class);
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 07f74367fd06..3f165a3bcb63 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1052,6 +1052,16 @@ flag {
}
flag {
+ name: "glanceable_hub_back_gesture"
+ namespace: "systemui"
+ description: "Enables back gesture on the glanceable hub"
+ bug: "346331399"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "glanceable_hub_allow_keyguard_when_dreaming"
namespace: "systemui"
description: "Allows users to exit dream to keyguard with glanceable hub enabled"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index cc4e7752ab21..d0466313cf81 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -27,6 +27,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.CommunalSwipeDetector
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
@@ -41,6 +42,7 @@ import com.android.compose.animation.scene.SwipeDirection
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
@@ -193,10 +195,17 @@ fun CommunalContainer(
Box(modifier = Modifier.fillMaxSize())
}
- scene(
- CommunalScenes.Communal,
- userActions = mapOf(Swipe(SwipeDirection.Right) to CommunalScenes.Blank)
- ) {
+ val userActions =
+ if (glanceableHubBackGesture()) {
+ mapOf(
+ Swipe(SwipeDirection.Right) to CommunalScenes.Blank,
+ Back to CommunalScenes.Blank,
+ )
+ } else {
+ mapOf(Swipe(SwipeDirection.Right) to CommunalScenes.Blank)
+ }
+
+ scene(CommunalScenes.Communal, userActions = userActions) {
CommunalScene(
backgroundType = backgroundType,
colors = colors,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index a614fc118636..4ef1f93481f7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -126,6 +126,8 @@ public class QuickStepContract {
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
// PiP animation is running
public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
+ // Communal hub is showing
+ public static final long SYSUI_STATE_COMMUNAL_HUB_SHOWING = 1L << 35;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -176,6 +178,7 @@ public class QuickStepContract {
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
+ SYSUI_STATE_COMMUNAL_HUB_SHOWING,
})
public @interface SystemUiStateFlags {}
@@ -283,6 +286,9 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
str.add("disable_gesture_pip_animating");
}
+ if ((flags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
+ str.add("communal_hub_showing");
+ }
return str.toString();
}
@@ -336,7 +342,8 @@ public class QuickStepContract {
// the keyguard)
if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0
|| (sysuiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0
- || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) {
+ || (sysuiStateFlags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0
+ || (sysuiStateFlags & SYSUI_STATE_COMMUNAL_HUB_SHOWING) != 0) {
return false;
}
if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) {
diff --git a/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
index af29b05a3fb1..8d1de0e65da5 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/log/DebugLogger.kt
@@ -62,6 +62,8 @@ object DebugLogger {
* @param error: a [Throwable] to log.
* @param message: a lazily evaluated message you wish to log.
*/
+ @JvmOverloads
+ @JvmName("logcatMessage")
inline fun Any.debugLog(
enabled: Boolean = Build.IS_DEBUGGABLE,
priority: Int = Log.DEBUG,
diff --git a/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
index 2764a1fdfe3d..e29ce2da9970 100644
--- a/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/log/DebugLogger.kt
@@ -22,6 +22,7 @@ import android.util.Log
/** An empty logger for release builds. */
object DebugLogger {
+ @JvmOverloads
@JvmName("logcatMessage")
inline fun Any.debugLog(
enabled: Boolean = Build.IS_DEBUGGABLE,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 37e9dc1a9d48..7750f6bf4178 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -233,7 +233,7 @@ public class SystemActions implements CoreStartable, ConfigurationController.Con
// NotificationShadeWindowController.registerCallback() only keeps weak references.
mNotificationShadeCallback =
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
- panelExpanded, isDreaming) ->
+ panelExpanded, isDreaming, communalShowing) ->
registerOrUnregisterDismissNotificationShadeAction();
mScreenshotHelper = new ScreenshotHelper(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 5084944685b6..42f66cca2522 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -18,12 +18,14 @@ package com.android.systemui.model
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE
import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
@@ -105,6 +107,10 @@ constructor(
{
it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion
},
+ SYSUI_STATE_COMMUNAL_HUB_SHOWING to
+ {
+ glanceableHubBackGesture() && it.scene == Scenes.Communal
+ }
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 1780b828cfa1..b3624ad4ad82 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -26,11 +26,13 @@ import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+import static com.android.systemui.Flags.glanceableHubBackGesture;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_COMMUNAL_HUB_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
@@ -792,7 +794,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
- boolean panelExpanded, boolean isDreaming) {
+ boolean panelExpanded, boolean isDreaming, boolean communalShowing) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
@@ -802,6 +804,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
+ .setFlag(SYSUI_STATE_COMMUNAL_HUB_SHOWING,
+ glanceableHubBackGesture() && communalShowing)
.commitUpdate(mContext.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index c4f6cd9aac29..8feefa4eca0f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -62,6 +62,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLogger.UiEventEnum;
import com.android.settingslib.Utils;
import com.android.systemui.Flags;
+import com.android.systemui.log.DebugLogger;
import com.android.systemui.res.R;
import com.android.systemui.screenshot.scroll.CropView;
import com.android.systemui.settings.UserTracker;
@@ -307,13 +308,16 @@ public class AppClipsActivity extends ComponentActivity {
&& mViewModel.getBacklinksLiveData().getValue() != null) {
ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData();
data.putParcelable(EXTRA_CLIP_DATA, backlinksData);
+
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "setResultThenFinish: sending notes app ClipData");
}
try {
mResultReceiver.send(Activity.RESULT_OK, data);
logUiEvent(SCREENSHOT_FOR_NOTE_ACCEPTED);
} catch (Exception e) {
- Log.e(TAG, "Error while returning data to trampoline activity", e);
+ Log.e(TAG, "Error while sending data to trampoline activity", e);
}
// Nullify the ResultReceiver before finishing to avoid resending the result.
@@ -354,6 +358,7 @@ public class AppClipsActivity extends ComponentActivity {
}
} catch (Exception e) {
// Do nothing.
+ Log.e(TAG, "Error while sending trampoline activity error code: " + errorCode, e);
}
// Nullify the ResultReceiver to avoid resending the result.
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 0161f787b459..ef18fbe6db8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -53,6 +53,7 @@ import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.DebugLogger;
import com.android.systemui.notetask.NoteTaskController;
import com.android.systemui.notetask.NoteTaskEntryPoint;
import com.android.systemui.res.R;
@@ -265,11 +266,15 @@ public class AppClipsTrampolineActivity extends Activity {
if (statusCode == CAPTURE_CONTENT_FOR_NOTE_SUCCESS) {
Uri uri = resultData.getParcelable(EXTRA_SCREENSHOT_URI, Uri.class);
convertedData.setData(uri);
- }
- if (resultData.containsKey(EXTRA_CLIP_DATA)) {
- ClipData backlinksData = resultData.getParcelable(EXTRA_CLIP_DATA, ClipData.class);
- convertedData.setClipData(backlinksData);
+ if (resultData.containsKey(EXTRA_CLIP_DATA)) {
+ ClipData backlinksData = resultData.getParcelable(EXTRA_CLIP_DATA,
+ ClipData.class);
+ convertedData.setClipData(backlinksData);
+
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "onReceiveResult: sending notes app ClipData");
+ }
}
// Broadcast no longer required, setting it to null.
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index d30d518f5777..8c833eccb1fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -54,6 +54,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.DebugLogger;
import com.android.systemui.screenshot.AssistContentRequester;
import com.android.systemui.screenshot.ImageExporter;
@@ -143,6 +144,7 @@ final class AppClipsViewModel extends ViewModel {
* @param displayId id of the display to query tasks for Backlinks data
*/
void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) {
+ DebugLogger.INSTANCE.logcatMessage(this, () -> "Backlinks triggered");
mBgExecutor.execute(() -> {
ListenableFuture<InternalBacklinksData> backlinksData = getBacklinksData(
taskIdsToIgnore, displayId);
@@ -247,6 +249,10 @@ final class AppClipsViewModel extends ViewModel {
}
private boolean shouldIncludeTask(RootTaskInfo taskInfo, Set<Integer> taskIdsToIgnore) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> String.format("shouldIncludeTask taskId %d; topActivity %s", taskInfo.taskId,
+ taskInfo.topActivity));
+
// Only consider tasks that shouldn't be ignored, are visible, running, and have a launcher
// icon. Furthermore, types such as launcher/home/dock/assistant are ignored.
return !taskIdsToIgnore.contains(taskInfo.taskId)
@@ -267,6 +273,10 @@ final class AppClipsViewModel extends ViewModel {
private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskId(
RootTaskInfo taskInfo) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> String.format("getBacklinksDataForTaskId for taskId %d; topActivity %s",
+ taskInfo.taskId, taskInfo.topActivity));
+
SettableFuture<InternalBacklinksData> backlinksData = SettableFuture.create();
int taskId = taskInfo.taskId;
mAssistContentRequester.requestAssistContent(taskId, assistContent ->
@@ -295,6 +305,10 @@ final class AppClipsViewModel extends ViewModel {
*/
private InternalBacklinksData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
@Nullable AssistContent content) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> String.format("getBacklinksDataFromAssistContent taskId %d; topActivity %s",
+ taskInfo.taskId, taskInfo.topActivity));
+
String appName = getAppNameOfTask(taskInfo);
String packageName = taskInfo.topActivity.getPackageName();
Drawable appIcon = taskInfo.topActivityInfo.loadIcon(mPackageManager);
@@ -307,22 +321,34 @@ final class AppClipsViewModel extends ViewModel {
// First preference is given to app provided uri.
if (content.isAppProvidedWebUri()) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "getBacklinksDataFromAssistContent: app has provided a uri");
+
Uri uri = content.getWebUri();
Intent backlinksIntent = new Intent(ACTION_VIEW).setData(uri);
if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "getBacklinksDataFromAssistContent: using app provided uri");
return new InternalBacklinksData(ClipData.newRawUri(appName, uri), appIcon);
}
}
// Second preference is given to app provided, hopefully deep-linking, intent.
if (content.isAppProvidedIntent()) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "getBacklinksDataFromAssistContent: app has provided an intent");
+
Intent backlinksIntent = content.getIntent();
if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "getBacklinksDataFromAssistContent: using app provided intent");
return new InternalBacklinksData(ClipData.newIntent(appName, backlinksIntent),
appIcon);
}
}
+ DebugLogger.INSTANCE.logcatMessage(this,
+ () -> "getBacklinksDataFromAssistContent: using fallback");
return fallback;
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ba4c29aad6a2..d870fe69463d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade
import android.content.Context
+import android.graphics.Insets
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
@@ -25,6 +26,7 @@ import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
@@ -37,6 +39,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.glanceableHubBackGesture
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -259,15 +262,33 @@ constructor(
// Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not
// occluded.
lifecycleRegistry.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- val exclusionRect =
- Rect(
- 0,
- topEdgeSwipeRegionWidth,
- containerView.right,
- containerView.bottom - bottomEdgeSwipeRegionWidth
- )
+ // Avoid adding exclusion to right/left edges to allow back gestures.
+ val insets =
+ if (glanceableHubBackGesture()) {
+ containerView.rootWindowInsets.getInsets(WindowInsets.Type.systemGestures())
+ } else {
+ Insets.NONE
+ }
- containerView.systemGestureExclusionRects = listOf(exclusionRect)
+ containerView.systemGestureExclusionRects =
+ listOf(
+ // Only allow swipe up to bouncer and swipe down to shade in the very
+ // top/bottom to avoid conflicting with widgets in the hub grid.
+ Rect(
+ insets.left,
+ topEdgeSwipeRegionWidth,
+ containerView.right - insets.right,
+ containerView.bottom - bottomEdgeSwipeRegionWidth
+ ),
+ // Disable back gestures on the left side of the screen, to avoid
+ // conflicting with scene transitions.
+ Rect(
+ 0,
+ 0,
+ insets.right,
+ containerView.bottom,
+ )
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f67d0c185e0f..bc5cf2a87925 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -644,7 +644,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mCurrentState.bouncerShowing,
mCurrentState.dozing,
mCurrentState.shadeOrQsExpanded,
- mCurrentState.dreaming);
+ mCurrentState.dreaming,
+ mCurrentState.communalVisible);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index da91d6a05d6b..6ac7f11d3ad8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -26,5 +26,5 @@ public interface StatusBarWindowCallback {
*/
void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
- boolean panelExpanded, boolean isDreaming);
+ boolean panelExpanded, boolean isDreaming, boolean communalShowing);
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 8457bdb2d0ff..45799b255630 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -261,7 +261,7 @@ public class BubblesManager {
// Store callback in a field so it won't get GC'd
mStatusBarWindowCallback =
(keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
- panelExpanded, isDreaming) -> {
+ panelExpanded, isDreaming, communalShowing) -> {
if (panelExpanded != mPanelExpanded) {
mPanelExpanded = panelExpanded;
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index b0213a4b6421..169511f827ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -16,19 +16,24 @@
package com.android.systemui.shade
+import android.graphics.Insets
import android.graphics.Rect
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
import android.view.MotionEvent
import android.view.View
+import android.view.WindowInsets
import android.widget.FrameLayout
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_BACK_GESTURE
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
@@ -66,6 +71,9 @@ import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@@ -317,6 +325,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
fun gestureExclusionZone_setAfterInit() =
with(kosmos) {
testScope.runTest {
@@ -325,10 +334,41 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
assertThat(containerView.systemGestureExclusionRects)
.containsExactly(
Rect(
- /* left */ 0,
- /* top */ TOP_SWIPE_REGION_WIDTH,
- /* right */ CONTAINER_WIDTH,
- /* bottom */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ /* left= */ 0,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ 0,
+ /* top= */ 0,
+ /* right= */ 0,
+ /* bottom= */ CONTAINER_HEIGHT
+ )
+ )
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_BACK_GESTURE)
+ fun gestureExclusionZone_setAfterInit_backGestureEnabled() =
+ with(kosmos) {
+ testScope.runTest {
+ goToScene(CommunalScenes.Communal)
+
+ assertThat(containerView.systemGestureExclusionRects)
+ .containsExactly(
+ Rect(
+ /* left= */ FAKE_INSETS.left,
+ /* top= */ TOP_SWIPE_REGION_WIDTH,
+ /* right= */ CONTAINER_WIDTH - FAKE_INSETS.right,
+ /* bottom= */ CONTAINER_HEIGHT - BOTTOM_SWIPE_REGION_WIDTH
+ ),
+ Rect(
+ /* left= */ 0,
+ /* top= */ 0,
+ /* right= */ FAKE_INSETS.right,
+ /* bottom= */ CONTAINER_HEIGHT
)
)
}
@@ -340,6 +380,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testScope.runTest {
goToScene(CommunalScenes.Communal)
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
+
// Shade shows up.
shadeTestUtil.setQsExpansion(1.0f)
testableLooper.processAllMessages()
@@ -355,6 +398,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
testScope.runTest {
goToScene(CommunalScenes.Communal)
+ // Exclusion rect is set.
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
+
// Bouncer is visible.
fakeKeyguardBouncerRepository.setPrimaryShow(true)
testableLooper.processAllMessages()
@@ -371,7 +417,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
goToScene(CommunalScenes.Communal)
// Exclusion rect is set.
- assertThat(containerView.systemGestureExclusionRects).hasSize(1)
+ assertThat(containerView.systemGestureExclusionRects).isNotEmpty()
// Leave the hub.
goToScene(CommunalScenes.Blank)
@@ -399,7 +445,12 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
private fun initAndAttachContainerView() {
- containerView = View(context)
+ val mockInsets =
+ mock<WindowInsets> {
+ on { getInsets(WindowInsets.Type.systemGestures()) } doReturn FAKE_INSETS
+ }
+
+ containerView = spy(View(context)) { on { rootWindowInsets } doReturn mockInsets }
parentView = FrameLayout(context)
@@ -422,6 +473,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
private const val RIGHT_SWIPE_REGION_WIDTH = 20
private const val TOP_SWIPE_REGION_WIDTH = 12
private const val BOTTOM_SWIPE_REGION_WIDTH = 14
+ private val FAKE_INSETS = Insets.of(10, 20, 30, 50)
/**
* A touch down event right in the middle of the screen, to avoid being in any of the swipe
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 3883604b7134..bd759f378d5b 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -25,6 +25,9 @@ import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX;
import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN;
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.os.UserHandle.getCallingUserId;
+import static android.os.UserManager.isVisibleBackgroundUsersEnabled;
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
@@ -80,6 +83,7 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.accessibility.Flags;
import com.android.server.display.feature.DisplayManagerFlags;
+import com.android.server.pm.UserManagerService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -186,9 +190,14 @@ public final class ColorDisplayService extends SystemService {
private final Object mCctTintApplierLock = new Object();
+ private final boolean mVisibleBackgroundUsersEnabled;
+ private final UserManagerService mUserManager;
+
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
+ mVisibleBackgroundUsersEnabled = isVisibleBackgroundUsersEnabled();
+ mUserManager = UserManagerService.getInstance();
}
@Override
@@ -1745,6 +1754,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public void setColorMode(int colorMode) {
setColorMode_enforcePermission();
+
+ enforceValidCallingUser("setColorMode");
+
final long token = Binder.clearCallingIdentity();
try {
setColorModeInternal(colorMode);
@@ -1784,6 +1796,9 @@ public final class ColorDisplayService extends SystemService {
if (!hasTransformsPermission && !hasLegacyPermission) {
throw new SecurityException("Permission required to set display saturation level");
}
+
+ enforceValidCallingUser("setSaturationLevel");
+
final long token = Binder.clearCallingIdentity();
try {
setSaturationLevelInternal(level);
@@ -1812,6 +1827,8 @@ public final class ColorDisplayService extends SystemService {
public boolean setAppSaturationLevel(String packageName, int level) {
super.setAppSaturationLevel_enforcePermission();
+ enforceValidCallingUser("setAppSaturationLevel");
+
final String callingPackageName = LocalServices.getService(PackageManagerInternal.class)
.getNameForUid(Binder.getCallingUid());
final long token = Binder.clearCallingIdentity();
@@ -1838,6 +1855,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setNightDisplayActivated(boolean activated) {
setNightDisplayActivated_enforcePermission();
+
+ enforceValidCallingUser("setNightDisplayActivated");
+
final long token = Binder.clearCallingIdentity();
try {
mNightDisplayTintController.setActivated(activated);
@@ -1861,6 +1881,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setNightDisplayColorTemperature(int temperature) {
setNightDisplayColorTemperature_enforcePermission();
+
+ enforceValidCallingUser("setNightDisplayColorTemperature");
+
final long token = Binder.clearCallingIdentity();
try {
return mNightDisplayTintController.setColorTemperature(temperature);
@@ -1883,6 +1906,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setNightDisplayAutoMode(int autoMode) {
setNightDisplayAutoMode_enforcePermission();
+
+ enforceValidCallingUser("setNightDisplayAutoMode");
+
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayAutoModeInternal(autoMode);
@@ -1917,6 +1943,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setNightDisplayCustomStartTime(Time startTime) {
setNightDisplayCustomStartTime_enforcePermission();
+
+ enforceValidCallingUser("setNightDisplayCustomStartTime");
+
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayCustomStartTimeInternal(startTime);
@@ -1939,6 +1968,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setNightDisplayCustomEndTime(Time endTime) {
setNightDisplayCustomEndTime_enforcePermission();
+
+ enforceValidCallingUser("setNightDisplayCustomEndTime");
+
final long token = Binder.clearCallingIdentity();
try {
return setNightDisplayCustomEndTimeInternal(endTime);
@@ -1961,6 +1993,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setDisplayWhiteBalanceEnabled(boolean enabled) {
setDisplayWhiteBalanceEnabled_enforcePermission();
+
+ enforceValidCallingUser("setDisplayWhiteBalanceEnabled");
+
final long token = Binder.clearCallingIdentity();
try {
return setDisplayWhiteBalanceSettingEnabled(enabled);
@@ -1993,6 +2028,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setReduceBrightColorsActivated(boolean activated) {
setReduceBrightColorsActivated_enforcePermission();
+
+ enforceValidCallingUser("setReduceBrightColorsActivated");
+
final long token = Binder.clearCallingIdentity();
try {
return setReduceBrightColorsActivatedInternal(activated);
@@ -2025,6 +2063,9 @@ public final class ColorDisplayService extends SystemService {
@Override
public boolean setReduceBrightColorsStrength(int strength) {
setReduceBrightColorsStrength_enforcePermission();
+
+ enforceValidCallingUser("setReduceBrightColorsStrength");
+
final long token = Binder.clearCallingIdentity();
try {
return setReduceBrightColorsStrengthInternal(strength);
@@ -2064,4 +2105,32 @@ public final class ColorDisplayService extends SystemService {
}
}
}
+
+ /**
+ * This method validates whether the calling user is allowed to set display's color transform
+ * on a device that enables visible background users.
+ * Only system or current user or the user that belongs to the same profile group as the current
+ * user is permitted to set the color transform.
+ */
+ private void enforceValidCallingUser(String method) {
+ if (!mVisibleBackgroundUsersEnabled) {
+ return;
+ }
+
+ int callingUserId = getCallingUserId();
+ if (callingUserId == USER_SYSTEM || callingUserId == mCurrentUser) {
+ return;
+ }
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mUserManager.isSameProfileGroup(callingUserId, mCurrentUser)) {
+ return;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ throw new SecurityException("Calling user id: " + callingUserId
+ + ", is not permitted to use Method " + method + "().");
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d9adb0e05a3b..efe07dc36646 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -53,6 +53,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
+import static android.app.WindowConfiguration.isFloating;
import static android.app.admin.DevicePolicyResources.Drawables.Source.PROFILE_SWITCH_ANIMATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.OUTLINE;
import static android.app.admin.DevicePolicyResources.Drawables.WORK_PROFILE_ICON;
@@ -342,6 +343,7 @@ import android.service.contentcapture.ActivityEvent;
import android.service.dreams.DreamActivity;
import android.service.voice.IVoiceInteractionSession;
import android.util.ArraySet;
+import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
import android.util.MergedConfiguration;
@@ -2923,14 +2925,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/** Makes starting window always fill the associated task. */
private void attachStartingSurfaceToAssociatedTask() {
- if (mSyncState == SYNC_STATE_NONE && isEmbedded()) {
- // Collect this activity since it's starting window will reparent to task. To ensure
- // any starting window's transaction will occur in order.
- mTransitionController.collect(this);
- }
+ mTransitionController.collect(mStartingWindow);
// Associate the configuration of starting window with the task.
overrideConfigurationPropagation(mStartingWindow, mStartingData.mAssociatedTask);
- getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
+ mStartingWindow.getSyncTransaction().reparent(mStartingWindow.mSurfaceControl,
mStartingData.mAssociatedTask.mSurfaceControl);
}
@@ -8685,7 +8683,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
}
- applySizeOverrideIfNeeded(newParentConfiguration, resolvedConfig);
+ applySizeOverrideIfNeeded(newParentConfiguration, parentWindowingMode, resolvedConfig);
mResolveConfigHint.resetTmpOverrides();
logAppCompatState();
@@ -8708,15 +8706,85 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* TODO: Consider integrate this with computeConfigByResolveHint()
*/
private void applySizeOverrideIfNeeded(Configuration newParentConfiguration,
- Configuration inOutConfig) {
- applySizeOverride(
- mDisplayContent,
- info.applicationInfo,
- newParentConfiguration,
- inOutConfig,
- mOptOutEdgeToEdge,
- hasFixedRotationTransform(),
- getCompatDisplayInsets() != null);
+ int parentWindowingMode, Configuration inOutConfig) {
+ if (mDisplayContent == null) {
+ return;
+ }
+ final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
+ int rotation = newParentConfiguration.windowConfiguration.getRotation();
+ if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) {
+ rotation = mDisplayContent.getRotation();
+ }
+ if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForConfig
+ || getCompatDisplayInsets() != null
+ || (isFloating(parentWindowingMode)
+ // Check the requested windowing mode of activity as well in case it is
+ // switching between PiP and fullscreen.
+ && (inOutConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_UNDEFINED
+ || isFloating(inOutConfig.windowConfiguration.getWindowingMode())))
+ || rotation == ROTATION_UNDEFINED)) {
+ // If the insets configuration decoupled logic is not enabled for the app, or the app
+ // already has a compat override, or the context doesn't contain enough info to
+ // calculate the override, skip the override.
+ return;
+ }
+ // Make sure the orientation related fields will be updated by the override insets, because
+ // fixed rotation has assigned the fields from display's configuration.
+ if (hasFixedRotationTransform()) {
+ inOutConfig.windowConfiguration.setAppBounds(null);
+ inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+ inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
+ inOutConfig.orientation = ORIENTATION_UNDEFINED;
+ }
+
+ // Override starts here.
+ final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
+ final int dw = rotated ? mDisplayContent.mBaseDisplayHeight
+ : mDisplayContent.mBaseDisplayWidth;
+ final int dh = rotated ? mDisplayContent.mBaseDisplayWidth
+ : mDisplayContent.mBaseDisplayHeight;
+ final Rect nonDecorInsets = mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets;
+ // This should be the only place override the configuration for ActivityRecord. Override
+ // the value if not calculated yet.
+ Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ if (outAppBounds == null || outAppBounds.isEmpty()) {
+ inOutConfig.windowConfiguration.setAppBounds(parentBounds);
+ outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
+ outAppBounds.inset(nonDecorInsets);
+ }
+ float density = inOutConfig.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = newParentConfiguration.densityDpi;
+ }
+ density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+ inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
+ }
+ if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+ inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
+ }
+ if (inOutConfig.smallestScreenWidthDp
+ == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
+ && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ // For the case of PIP transition and multi-window environment, the
+ // smallestScreenWidthDp is handled already. Override only if the app is in
+ // fullscreen.
+ final DisplayInfo info = new DisplayInfo(mDisplayContent.getDisplayInfo());
+ mDisplayContent.computeSizeRanges(info, rotated, dw, dh,
+ mDisplayContent.getDisplayMetrics().density,
+ inOutConfig, true /* overrideConfig */);
+ }
+
+ // It's possible that screen size will be considered in different orientation with or
+ // without considering the system bar insets. Override orientation as well.
+ if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
+ inOutConfig.orientation =
+ (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
+ ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+ }
}
private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index d65a106a1079..00b6453b2484 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2737,9 +2737,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final ActivityOptions activityOptions = options != null
? options.getOptions(this)
: null;
- boolean moveHomeTaskForward = true;
synchronized (mService.mGlobalLock) {
final boolean isCallerRecents = mRecentTasks.isCallerRecents(callingUid);
+ boolean moveHomeTaskForward = isCallerRecents;
int activityType = ACTIVITY_TYPE_UNDEFINED;
if (activityOptions != null) {
activityType = activityOptions.getLaunchActivityType();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 14e256f7a815..8421765060ce 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -210,6 +210,9 @@ class BackNavigationController {
+ "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
currentTask, currentActivity, callbackInfo, window);
+ // Clear the pointer down outside focus if any.
+ mWindowManagerService.clearPointerDownOutsideFocusRunnable();
+
// If we don't need to set up the animation, we return early. This is the case when
// - We have an application callback.
// - We don't have any ActivityRecord or Task to animate.
@@ -1361,6 +1364,8 @@ class BackNavigationController {
synchronized (openTask.mWmService.mGlobalLock) {
if (mRequestedStartingSurfaceId != INVALID_TASK_ID) {
mStartingSurface = sc;
+ } else {
+ sc.release();
}
}
}
@@ -1599,12 +1604,20 @@ class BackNavigationController {
@NonNull ActivityRecord[] visibleOpenActivities) {
boolean needsLaunchBehind = true;
if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
+ boolean activitiesAreDrawn = false;
+ for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
+ // If the activity hasn't stopped, it's window should remain drawn.
+ activitiesAreDrawn |= visibleOpenActivities[i].firstWindowDrawn;
+ }
final WindowContainer mainOpen = openAnimationAdaptor.mAdaptors[0].mTarget;
final TaskSnapshot snapshot = getSnapshot(mainOpen, visibleOpenActivities);
- openAnimationAdaptor.createStartingSurface(snapshot);
- // set LaunchBehind if we are creating splash screen surface.
- needsLaunchBehind = snapshot == null
- && openAnimationAdaptor.mRequestedStartingSurfaceId != INVALID_TASK_ID;
+ // Don't create starting surface if previous activities haven't stopped or
+ // the snapshot does not exist.
+ if (snapshot != null || !activitiesAreDrawn) {
+ openAnimationAdaptor.createStartingSurface(snapshot);
+ }
+ // Only use LaunchBehind if snapshot does not exist.
+ needsLaunchBehind = snapshot == null;
}
if (needsLaunchBehind) {
for (int i = visibleOpenActivities.length - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 70e6d5ddf3da..efd52026cfec 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -22,23 +22,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
-import static android.app.WindowConfiguration.isFloating;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.app.WindowConfigurationProto.WINDOWING_MODE;
import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
-import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED;
-import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Surface.ROTATION_270;
-import static android.view.Surface.ROTATION_90;
import static com.android.server.wm.ConfigurationContainerProto.FULL_CONFIGURATION;
import static com.android.server.wm.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
@@ -47,14 +38,11 @@ import static com.android.server.wm.ConfigurationContainerProto.OVERRIDE_CONFIGU
import android.annotation.CallSuper;
import android.annotation.NonNull;
import android.app.WindowConfiguration;
-import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.LocaleList;
-import android.util.DisplayMetrics;
import android.util.proto.ProtoOutputStream;
-import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
@@ -536,133 +524,22 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
}
/**
- * @see ActivityRecord#applySizeOverrideIfNeeded
- */
- public static boolean applySizeOverride(DisplayContent displayContent, ApplicationInfo appInfo,
- Configuration newParentConfiguration, Configuration inOutConfig,
- boolean optOutEdgeToEdge, boolean hasFixedRotationTransform,
- boolean hasCompatDisplayInsets) {
- if (displayContent == null) {
- return false;
- }
- final boolean useOverrideInsetsForConfig =
- displayContent.mWmService.mFlags.mInsetsDecoupledConfiguration
- ? !appInfo.isChangeEnabled(INSETS_DECOUPLED_CONFIGURATION_ENFORCED)
- && !appInfo.isChangeEnabled(
- OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION)
- : appInfo.isChangeEnabled(OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION);
- if (newParentConfiguration == null) {
- newParentConfiguration = displayContent.getConfiguration();
- }
- final int parentWindowingMode =
- newParentConfiguration.windowConfiguration.getWindowingMode();
- final boolean isFloating = isFloating(parentWindowingMode)
- // Check the requested windowing mode of activity as well in case it is
- // switching between PiP and fullscreen.
- && (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_UNDEFINED
- || isFloating(inOutConfig.windowConfiguration.getWindowingMode()));
- final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- int rotation = newParentConfiguration.windowConfiguration.getRotation();
- if (rotation == ROTATION_UNDEFINED && !hasFixedRotationTransform) {
- rotation = displayContent.getRotation();
- }
- if (!optOutEdgeToEdge && (!useOverrideInsetsForConfig
- || hasCompatDisplayInsets
- || isFloating
- || rotation == ROTATION_UNDEFINED)) {
- // If the insets configuration decoupled logic is not enabled for the app, or the app
- // already has a compat override, or the context doesn't contain enough info to
- // calculate the override, skip the override.
- return false;
- }
- // Make sure the orientation related fields will be updated by the override insets, because
- // fixed rotation has assigned the fields from display's configuration.
- if (hasFixedRotationTransform) {
- inOutConfig.windowConfiguration.setAppBounds(null);
- inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
- inOutConfig.smallestScreenWidthDp = Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- inOutConfig.orientation = ORIENTATION_UNDEFINED;
- }
-
- // Override starts here.
- final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
- final int dw = rotated
- ? displayContent.mBaseDisplayHeight
- : displayContent.mBaseDisplayWidth;
- final int dh = rotated
- ? displayContent.mBaseDisplayWidth
- : displayContent.mBaseDisplayHeight;
- final Rect nonDecorFrame = displayContent.getDisplayPolicy()
- .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorFrame;
- // This should be the only place override the configuration for ActivityRecord. Override
- // the value if not calculated yet.
- Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- if (outAppBounds == null || outAppBounds.isEmpty()) {
- inOutConfig.windowConfiguration.setAppBounds(parentBounds);
- outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
- outAppBounds.intersect(nonDecorFrame);
- }
- float density = inOutConfig.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = newParentConfiguration.densityDpi;
- }
- density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
- if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- inOutConfig.screenWidthDp = (int) (outAppBounds.width() / density + 0.5f);
- }
- if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- inOutConfig.screenHeightDp = (int) (outAppBounds.height() / density + 0.5f);
- }
- if (inOutConfig.smallestScreenWidthDp == Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED
- && parentWindowingMode == WINDOWING_MODE_FULLSCREEN) {
- // For the case of PIP transition and multi-window environment, the
- // smallestScreenWidthDp is handled already. Override only if the app is in
- // fullscreen.
- final DisplayInfo info = new DisplayInfo(displayContent.getDisplayInfo());
- displayContent.computeSizeRanges(info, rotated, dw, dh,
- displayContent.getDisplayMetrics().density,
- inOutConfig, true /* overrideConfig */);
- }
-
- // It's possible that screen size will be considered in different orientation with or
- // without considering the system bar insets. Override orientation as well.
- if (inOutConfig.orientation == ORIENTATION_UNDEFINED) {
- inOutConfig.orientation =
- (inOutConfig.screenWidthDp <= inOutConfig.screenHeightDp)
- ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
- }
- return true;
- }
-
- /**
- * Gives the derived class a chance to apply the app-specific configuration.
- *
- * @param inOutConfig the configuration as the requested configuration.
- * @return true if any of the given configuration has been updated.
- */
- public boolean onApplyAppSpecificConfig(Configuration inOutConfig) {
- return false;
- }
-
- /**
* Applies app-specific nightMode and {@link LocaleList} on requested configuration.
* @return true if any of the requested configuration has been updated.
*/
public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
@Configuration.GrammaticalGender Integer gender) {
mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
- boolean changed = onApplyAppSpecificConfig(mRequestsTmpConfig);
boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
nightMode);
boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
locales);
boolean newGenderSet = setOverrideGender(mRequestsTmpConfig,
gender == null ? Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED : gender);
- if (changed || newNightModeSet || newLocalesSet || newGenderSet) {
+ if (newNightModeSet || newLocalesSet || newGenderSet) {
onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
}
- return changed || newNightModeSet || newLocalesSet || newGenderSet;
+ return newNightModeSet || newLocalesSet || newGenderSet;
}
/**
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index c0713966d8de..3947d02c86c9 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -153,6 +153,10 @@ class UnknownAppVisibilityController {
mUnknownApps.put(activity, UNKNOWN_STATE_WAITING_VISIBILITY_UPDATE);
mDisplayContent.notifyKeyguardFlagsChanged();
notifyVisibilitiesUpdated();
+ } else if (state == UNKNOWN_STATE_WAITING_RESUME
+ && !activity.isState(ActivityRecord.State.RESUMED)) {
+ Slog.d(TAG, "UAVC: skip waiting for non-resumed relayouted " + activity);
+ mUnknownApps.remove(activity);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0c1ec5042c19..9a5f84cf7449 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -732,6 +732,13 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayWindowListenerController mDisplayNotificationController;
final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
+ /** Amount of time (in milliseconds) to delay the pointer down outside focus handling */
+ private static final int POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS = 50;
+
+ /** A runnable to handle pointer down outside focus event. */
+ @Nullable
+ private Runnable mPointerDownOutsideFocusRunnable;
+
boolean mDisplayFrozen = false;
long mDisplayFreezeTime = 0;
int mLastDisplayFreezeDuration = 0;
@@ -5896,7 +5903,8 @@ public class WindowManagerService extends IWindowManager.Stub
case ON_POINTER_DOWN_OUTSIDE_FOCUS: {
synchronized (mGlobalLock) {
final IBinder touchedToken = (IBinder) msg.obj;
- onPointerDownOutsideFocusLocked(getInputTargetFromToken(touchedToken));
+ onPointerDownOutsideFocusLocked(getInputTargetFromToken(touchedToken),
+ true /* fromHandler */);
}
break;
}
@@ -7922,7 +7930,8 @@ public class WindowManagerService extends IWindowManager.Stub
synchronized (mGlobalLock) {
final InputTarget inputTarget =
WindowManagerService.this.getInputTargetFromWindowTokenLocked(windowToken);
- WindowManagerService.this.onPointerDownOutsideFocusLocked(inputTarget);
+ WindowManagerService.this.onPointerDownOutsideFocusLocked(inputTarget,
+ false /* fromHandler */);
}
}
@@ -8997,39 +9006,78 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- private void onPointerDownOutsideFocusLocked(InputTarget t) {
+ void clearPointerDownOutsideFocusRunnable() {
+ if (mPointerDownOutsideFocusRunnable == null) return;
+
+ mH.removeCallbacks(mPointerDownOutsideFocusRunnable);
+ mPointerDownOutsideFocusRunnable = null;
+ }
+
+ private void onPointerDownOutsideFocusLocked(InputTarget t, boolean fromHandler) {
if (t == null || !t.receiveFocusFromTapOutside()) {
// If the window that received the input event cannot receive keys, don't move the
// display it's on to the top since that window won't be able to get focus anyway.
return;
}
- if (mRecentsAnimationController != null
- && mRecentsAnimationController.getTargetAppMainWindow() == t) {
- // If there is an active recents animation and touched window is the target, then ignore
- // the touch. The target already handles touches using its own input monitor and we
- // don't want to trigger any lifecycle changes from focusing another window.
- // TODO(b/186770026): We should remove this once we support multiple resumed activities
- // while in overview
- return;
- }
+ clearPointerDownOutsideFocusRunnable();
+
+ // For embedded activity that is showing side-by-side with another activity, delay
+ // handling the touch-outside event to prevent focus rapid changes back-n-forth.
+ // Otherwise, handle the touch-outside event directly.
final WindowState w = t.getWindowState();
- if (w != null) {
- final Task task = w.getTask();
- if (task != null && w.mTransitionController.isTransientHide(task)) {
- // Don't disturb transient animation by accident touch.
+ final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
+ if (activity != null && activity.isEmbedded()
+ && activity.getTaskFragment().getAdjacentTaskFragment() != null) {
+ mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
+ } else if (!fromHandler) {
+ // Still post the runnable to handler thread in case there is already a runnable
+ // in execution, but still waiting to hold the wm lock.
+ mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
+ mH.post(mPointerDownOutsideFocusRunnable);
+ } else {
+ handlePointerDownOutsideFocus(t);
+ }
+ }
+
+ private void handlePointerDownOutsideFocus(InputTarget t) {
+ synchronized (mGlobalLock) {
+ if (mPointerDownOutsideFocusRunnable != null
+ && mH.hasCallbacks(mPointerDownOutsideFocusRunnable)) {
+ // Skip if there's another pending pointer-down-outside-focus event.
+ return;
+ }
+ clearPointerDownOutsideFocusRunnable();
+
+ if (mRecentsAnimationController != null
+ && mRecentsAnimationController.getTargetAppMainWindow() == t) {
+ // If there is an active recents animation and touched window is the target,
+ // then ignore the touch. The target already handles touches using its own
+ // input monitor and we don't want to trigger any lifecycle changes from
+ // focusing another window.
+ // TODO(b/186770026): We should remove this once we support multiple resumed
+ // activities while in overview
return;
}
- }
- ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s",
- t);
- if (mFocusedInputTarget != t && mFocusedInputTarget != null) {
- mFocusedInputTarget.handleTapOutsideFocusOutsideSelf();
+ final WindowState w = t.getWindowState();
+ if (w != null) {
+ final Task task = w.getTask();
+ if (task != null && w.mTransitionController.isTransientHide(task)) {
+ // Don't disturb transient animation by accident touch.
+ return;
+ }
+ }
+
+ ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "onPointerDownOutsideFocusLocked called on %s", t);
+ if (mFocusedInputTarget != t && mFocusedInputTarget != null) {
+ mFocusedInputTarget.handleTapOutsideFocusOutsideSelf();
+ }
+ // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities.
+ mAtmService.mTaskSupervisor.mUserLeaving = true;
+ t.handleTapOutsideFocusInsideSelf();
+ mAtmService.mTaskSupervisor.mUserLeaving = false;
}
- // Trigger Activity#onUserLeaveHint() if the order change of task pauses any activities.
- mAtmService.mTaskSupervisor.mUserLeaving = true;
- t.handleTapOutsideFocusInsideSelf();
- mAtmService.mTaskSupervisor.mUserLeaving = false;
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index f6942ee42773..60d3e787cac4 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1581,25 +1581,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
@Override
- public boolean onApplyAppSpecificConfig(Configuration inOutConfig) {
- if (mConfigActivityRecord != null) {
- // Let the activity decide whether to apply the size override.
- return false;
- }
- final DisplayContent displayContent = mAtm.mWindowManager != null
- ? mAtm.mWindowManager.getDefaultDisplayContentLocked()
- : null;
- return applySizeOverride(
- displayContent,
- mInfo,
- null /* newParentConfiguration */,
- inOutConfig,
- false /* optOutEdgeToEdge */,
- false /* hasFixedRotationTransform */,
- false /* hasCompatDisplayInsets */);
- }
-
- @Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
super.onConfigurationChanged(newGlobalConfig);
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 64dbc50b8dd2..c76bcf804ee4 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -287,7 +287,7 @@ public final class ProfcollectForwardingService extends SystemService {
if (randomNum < traceFrequency) {
BackgroundThread.get().getThreadHandler().post(() -> {
try {
- mIProfcollect.trace_once("applaunch");
+ mIProfcollect.trace_system("applaunch");
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
@@ -327,7 +327,7 @@ public final class ProfcollectForwardingService extends SystemService {
// Dex2oat could take a while before it starts. Add a short delay before start tracing.
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- mIProfcollect.trace_once("dex2oat");
+ mIProfcollect.trace_system("dex2oat");
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
@@ -404,7 +404,7 @@ public final class ProfcollectForwardingService extends SystemService {
String traceTag = traceInitialization ? "camera_init" : "camera";
BackgroundThread.get().getThreadHandler().postDelayed(() -> {
try {
- mIProfcollect.trace_once(traceTag);
+ mIProfcollect.trace_system(traceTag);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to initiate trace: " + e.getMessage());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
index 35cff7a7a324..7efbc88833cf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ProtoLogIntegrationTest.java
@@ -25,11 +25,11 @@ import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
-import com.android.internal.protolog.ProtoLog;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.LogLevel;
+import com.android.internal.protolog.ProtoLog;
import org.junit.After;
import org.junit.Ignore;
@@ -53,6 +53,9 @@ public class ProtoLogIntegrationTest {
runWith(mockedProtoLog, this::testProtoLog);
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(ProtoLogGroup.TEST_GROUP),
anyInt(), eq(0b0010010111),
+ eq(com.android.internal.protolog.ProtoLogGroup.TEST_GROUP.isLogToLogcat()
+ ? "Test completed successfully: %b %d %x %f %% %s"
+ : null),
eq(new Object[]{true, 1L, 2L, 0.3, "ok"}));
}
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5a48327e7576..5a27593c7a36 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
+import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@ public class LegacyProtoLogImplTest {
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024, () -> {});
+ 1024 * 1024, mReader, 1024, new TreeMap<>(), () -> {});
}
@After
@@ -141,7 +142,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -158,7 +159,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -175,7 +176,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -191,7 +192,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -207,7 +208,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -276,7 +277,7 @@ public class LegacyProtoLogImplTest {
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b1110101001010100,
+ 0b1110101001010100, null,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -301,7 +302,7 @@ public class LegacyProtoLogImplTest {
long before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b01100100,
+ 0b01100100, null,
new Object[]{"test", 1, 0.1, true});
long after = SystemClock.elapsedRealtimeNanos();
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
@@ -325,7 +326,7 @@ public class LegacyProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
mProtoLog.startProtoLog(mock(PrintWriter.class));
mProtoLog.log(LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234,
- 0b11, new Object[]{true});
+ 0b11, null, new Object[]{true});
mProtoLog.stopProtoLog(mock(PrintWriter.class), true);
try (InputStream is = new FileInputStream(mFile)) {
ProtoInputStream ip = new ProtoInputStream(is);
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index b6672a0e2f4b..1d7b6b348e10 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -60,9 +60,6 @@ import org.junit.runners.JUnit4;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import perfetto.protos.Protolog;
-import perfetto.protos.ProtologCommon;
-
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -70,6 +67,9 @@ import java.util.Random;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
+import perfetto.protos.Protolog;
+import perfetto.protos.ProtologCommon;
+
/**
* Test class for {@link ProtoLogImpl}.
*/
@@ -111,9 +111,6 @@ public class PerfettoProtoLogImplTest {
//noinspection ResultOfMethodCallIgnored
mFile.delete();
- TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
- TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
-
mViewerConfigBuilder = Protolog.ProtoLogViewerConfig.newBuilder()
.addGroups(
Protolog.ProtoLogViewerConfig.Group.newBuilder()
@@ -160,9 +157,8 @@ public class PerfettoProtoLogImplTest {
mCacheUpdater = () -> {};
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
mProtoLog = new PerfettoProtoLogImpl(
- viewerConfigInputStreamProvider, mReader,
+ viewerConfigInputStreamProvider, mReader, new TreeMap<>(),
() -> mCacheUpdater.run());
- mProtoLog.registerGroups(TestProtoLogGroup.values());
}
@After
@@ -214,15 +210,15 @@ public class PerfettoProtoLogImplTest {
// Shouldn't be logging anything except WTF unless explicitly requested in the group
// override.
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -244,15 +240,15 @@ public class PerfettoProtoLogImplTest {
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -278,15 +274,15 @@ public class PerfettoProtoLogImplTest {
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -308,15 +304,15 @@ public class PerfettoProtoLogImplTest {
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.VERBOSE, TestProtoLogGroup.TEST_GROUP, 2,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WARN, TestProtoLogGroup.TEST_GROUP, 3,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.ERROR, TestProtoLogGroup.TEST_GROUP, 4,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
mProtoLog.log(LogLevel.WTF, TestProtoLogGroup.TEST_GROUP, 5,
- LogDataType.BOOLEAN, new Object[]{true});
+ LogDataType.BOOLEAN, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -333,14 +329,14 @@ public class PerfettoProtoLogImplTest {
}
@Test
- public void log_logcatEnabled() {
+ public void log_logcatEnabledExternalMessage() {
when(mReader.getViewerString(anyLong())).thenReturn("test %b %d %% 0x%x %s %f");
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 30000, "test", 0.000003});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
@@ -357,17 +353,32 @@ public class PerfettoProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{true, 10000, 0.0001, 0.00002, "test"});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
LogLevel.INFO),
- eq("FORMAT_ERROR \"test %b %d %% %x %s %f\", "
- + "args=(true, 10000, 1.0E-4, 2.0E-5, test)"));
+ eq("UNKNOWN MESSAGE (1234) true 10000 1.0E-4 2.0E-5 test"));
verify(mReader).getViewerString(eq(1234L));
}
@Test
+ public void log_logcatEnabledInlineMessage() {
+ when(mReader.getViewerString(anyLong())).thenReturn("test %d");
+ PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
+ TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+ TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
+
+ implSpy.log(
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
+ new Object[]{5});
+
+ verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
+ LogLevel.INFO), eq("test 5"));
+ verify(mReader, never()).getViewerString(anyLong());
+ }
+
+ @Test
public void log_logcatEnabledNoMessage() {
when(mReader.getViewerString(anyLong())).thenReturn(null);
PerfettoProtoLogImpl implSpy = Mockito.spy(mProtoLog);
@@ -375,11 +386,11 @@ public class PerfettoProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToProto(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, null,
new Object[]{5});
verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
- LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)"));
+ LogLevel.INFO), eq("UNKNOWN MESSAGE (1234) 5"));
verify(mReader).getViewerString(eq(1234L));
}
@@ -390,7 +401,7 @@ public class PerfettoProtoLogImplTest {
TestProtoLogGroup.TEST_GROUP.setLogToLogcat(false);
implSpy.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321,
+ LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d",
new Object[]{5});
verify(implSpy, never()).passToLogcat(any(), any(), any());
@@ -414,7 +425,7 @@ public class PerfettoProtoLogImplTest {
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b1110101001010100,
+ 0b1110101001010100, null,
new Object[]{"test", 1, 2, 3, 0.4, 0.5, 0.6, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -433,38 +444,6 @@ public class PerfettoProtoLogImplTest {
.isEqualTo("My test message :: test, 2, 4, 6, 0.400000, 5.000000e-01, 0.6, true");
}
- @Test
- public void log_noProcessing() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog().build();
- long before;
- long after;
- try {
- traceMonitor.start();
- assertTrue(mProtoLog.isProtoEnabled());
-
- before = SystemClock.elapsedRealtimeNanos();
- mProtoLog.log(
- LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
- "My test message :: %s, %d, %o, %x, %f, %b",
- "test", 1, 2, 3, 0.4, true);
- after = SystemClock.elapsedRealtimeNanos();
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
- .isAtLeast(before);
- Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
- .isAtMost(after);
- Truth.assertThat(protolog.messages.getFirst().getMessage())
- .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true");
- }
-
private long addMessageToConfig(ProtologCommon.ProtoLogLevel logLevel, String message) {
final long messageId = new Random().nextLong();
mViewerConfigBuilder.addMessages(Protolog.ProtoLogViewerConfig.MessageData.newBuilder()
@@ -491,7 +470,7 @@ public class PerfettoProtoLogImplTest {
before = SystemClock.elapsedRealtimeNanos();
mProtoLog.log(
LogLevel.INFO, TestProtoLogGroup.TEST_GROUP, messageHash,
- 0b01100100,
+ 0b01100100, null,
new Object[]{"test", 1, 0.1, true});
after = SystemClock.elapsedRealtimeNanos();
} finally {
@@ -509,7 +488,7 @@ public class PerfettoProtoLogImplTest {
try {
traceMonitor.start();
mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, new Object[]{true});
+ 0b11, null, new Object[]{true});
} finally {
traceMonitor.stop(mWriter);
}
@@ -533,7 +512,7 @@ public class PerfettoProtoLogImplTest {
ProtoLogImpl.setSingleInstance(mProtoLog);
ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1,
- 0b11, true);
+ 0b11, null, true);
} finally {
traceMonitor.stop(mWriter);
}
@@ -607,7 +586,7 @@ public class PerfettoProtoLogImplTest {
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
- Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isFalse();
+ Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF)).isTrue();
PerfettoTraceMonitor traceMonitor1 =
PerfettoTraceMonitor.newBuilder().enableProtoLog(true,
@@ -685,53 +664,7 @@ public class PerfettoProtoLogImplTest {
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.ERROR))
.isFalse();
Truth.assertThat(mProtoLog.isEnabled(TestProtoLogGroup.TEST_GROUP, LogLevel.WTF))
- .isFalse();
- }
-
- @Test
- public void supportsNullString() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
- .build();
-
- try {
- traceMonitor.start();
-
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
- "My test null string: %s", null);
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.get(0).getMessage())
- .isEqualTo("My test null string: null");
- }
-
- @Test
- public void supportNullParams() throws IOException {
- PerfettoTraceMonitor traceMonitor =
- PerfettoTraceMonitor.newBuilder().enableProtoLog(true)
- .build();
-
- try {
- traceMonitor.start();
-
- mProtoLog.log(LogLevel.DEBUG, TestProtoLogGroup.TEST_GROUP,
- "My null args: %d, %f, %b", null, null, null);
- } finally {
- traceMonitor.stop(mWriter);
- }
-
- final ResultReader reader = new ResultReader(mWriter.write(), mTraceConfig);
- final ProtoLogTrace protolog = reader.readProtoLogTrace();
-
- Truth.assertThat(protolog.messages).hasSize(1);
- Truth.assertThat(protolog.messages.get(0).getMessage())
- .isEqualTo("My null args: 0, 0, false");
+ .isTrue();
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
index 0496240f01e4..60456f9ea10f 100644
--- a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java
@@ -58,50 +58,51 @@ public class ProtoLogImplTest {
public void d_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.d(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.DEBUG), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void v_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.v(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.VERBOSE), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void i_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.i(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.INFO), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void w_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.w(TestProtoLogGroup.TEST_GROUP, 1234,
+ 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.WARN), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
public void e_logCalled() {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
- ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321);
+ ProtoLogImpl.e(TestProtoLogGroup.TEST_GROUP, 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.ERROR), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
@Test
@@ -109,10 +110,10 @@ public class ProtoLogImplTest {
IProtoLog mockedProtoLog = mock(IProtoLog.class);
ProtoLogImpl.setSingleInstance(mockedProtoLog);
ProtoLogImpl.wtf(TestProtoLogGroup.TEST_GROUP,
- 1234, 4321);
+ 1234, 4321, "test %d");
verify(mockedProtoLog).log(eq(LogLevel.WTF), eq(
TestProtoLogGroup.TEST_GROUP),
- eq(1234L), eq(4321), eq(new Object[]{}));
+ eq(1234L), eq(4321), eq("test %d"), eq(new Object[]{}));
}
private enum TestProtoLogGroup implements IProtoLogGroup {
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index 203832d2dbe8..8abc26d67c1f 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-#include <iostream>
+#include "xml/XmlPullParser.h"
+
+#include <algorithm>
#include <string>
+#include <tuple>
#include "util/Util.h"
-#include "xml/XmlPullParser.h"
#include "xml/XmlUtil.h"
using ::android::InputStream;
@@ -325,5 +327,18 @@ std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, St
return {};
}
+XmlPullParser::const_iterator XmlPullParser::FindAttribute(android::StringPiece namespace_uri,
+ android::StringPiece name) const {
+ const auto end_iter = end_attributes();
+ const auto iter = std::lower_bound(begin_attributes(), end_iter, std::tuple(namespace_uri, name),
+ [](const Attribute& attr, const auto& rhs) {
+ return std::tie(attr.namespace_uri, attr.name) < rhs;
+ });
+ if (iter != end_iter && namespace_uri == iter->namespace_uri && name == iter->name) {
+ return iter;
+ }
+ return end_iter;
+}
+
} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index 655e6dc93e75..64274d032c61 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -19,8 +19,7 @@
#include <expat.h>
-#include <algorithm>
-#include <istream>
+#include <optional>
#include <ostream>
#include <queue>
#include <stack>
@@ -302,31 +301,6 @@ inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
return compare(rhs) != 0;
}
-inline XmlPullParser::const_iterator XmlPullParser::FindAttribute(
- android::StringPiece namespace_uri, android::StringPiece name) const {
- const auto end_iter = end_attributes();
- const auto iter = std::lower_bound(
- begin_attributes(), end_iter,
- std::pair<android::StringPiece, android::StringPiece>(namespace_uri, name),
- [](const Attribute& attr,
- const std::pair<android::StringPiece, android::StringPiece>& rhs) -> bool {
- int cmp = attr.namespace_uri.compare(
- 0, attr.namespace_uri.size(), rhs.first.data(), rhs.first.size());
- if (cmp < 0) return true;
- if (cmp > 0) return false;
- cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(),
- rhs.second.size());
- if (cmp < 0) return true;
- return false;
- });
-
- if (iter != end_iter && namespace_uri == iter->namespace_uri &&
- name == iter->name) {
- return iter;
- }
- return end_iter;
-}
-
} // namespace xml
} // namespace aapt
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 3c99e68cd6a0..1087ae6ee41d 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -120,8 +120,6 @@ class ProtoLogCallProcessorImpl(
logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
call.name.toString(), call, context), groupMap.getValue(groupName))
- } else if (call.name.id == "initialize") {
- // No processing
} else {
// Process non-log message calls
otherCallVisitor?.processCall(call)
diff --git a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
index c478f5844de6..6a8a0717b2f1 100644
--- a/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/SourceTransformer.kt
@@ -130,27 +130,28 @@ class SourceTransformer(
val hash = CodeUtils.hash(packagePath, messageString, level, group)
val newCall = call.clone()
- // Remove message string.
- // Out: ProtoLog.e(GROUP, args)
- newCall.arguments.removeAt(1)
+ if (!group.textEnabled) {
+ // Remove message string if text logging is not enabled by default.
+ // Out: ProtoLog.e(GROUP, null, arg)
+ newCall.arguments[1].replace(NameExpr("null"))
+ }
// Insert message string hash as a second argument.
- // Out: ProtoLog.e(GROUP, 1234, args)
+ // Out: ProtoLog.e(GROUP, 1234, null, arg)
newCall.arguments.add(1, LongLiteralExpr("" + hash + "L"))
val argTypes = LogDataType.parseFormatString(messageString)
val typeMask = LogDataType.logDataTypesToBitMask(argTypes)
// Insert bitmap representing which Number parameters are to be considered as
// floating point numbers.
- // Out: ProtoLog.e(GROUP, 1234, 0, args)
+ // Out: ProtoLog.e(GROUP, 1234, 0, null, arg)
newCall.arguments.add(2, IntegerLiteralExpr(typeMask))
// Replace call to a stub method with an actual implementation.
- // Out: ProtoLogImpl.e(GROUP, 1234, 0, args)
+ // Out: ProtoLogImpl.e(GROUP, 1234, null, arg)
newCall.setScope(protoLogImplClassNode)
if (argTypes.size != call.arguments.size - 2) {
throw InvalidProtoLogCallException(
"Number of arguments (${argTypes.size} does not match format" +
" string in: $call", ParsingContext(path, call))
}
- val argsOffset = 3
val blockStmt = BlockStmt()
if (argTypes.isNotEmpty()) {
// Assign every argument to a variable to check its type in compile time
@@ -159,9 +160,9 @@ class SourceTransformer(
argTypes.forEachIndexed { idx, type ->
val varName = "protoLogParam$idx"
val declaration = VariableDeclarator(getASTTypeForDataType(type), varName,
- getConversionForType(type)(newCall.arguments[idx + argsOffset].clone()))
+ getConversionForType(type)(newCall.arguments[idx + 4].clone()))
blockStmt.addStatement(ExpressionStmt(VariableDeclarationExpr(declaration)))
- newCall.setArgument(idx + argsOffset, NameExpr(SimpleName(varName)))
+ newCall.setArgument(idx + 4, NameExpr(SimpleName(varName)))
}
} else {
// Assign (Object[])null as the vararg parameter to prevent allocating an empty
diff --git a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
index 0cbbd483fe59..2a8367787ba1 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/EndToEndTest.kt
@@ -60,7 +60,7 @@ class EndToEndTest {
.containsMatch(Pattern.compile("\\{ String protoLogParam0 = " +
"String\\.valueOf\\(argString\\); long protoLogParam1 = argInt; " +
"com\\.android\\.internal\\.protolog.ProtoLogImpl_.*\\.d\\(" +
- "GROUP, -6872339441335321086L, 4, protoLogParam0, protoLogParam1" +
+ "GROUP, -6872339441335321086L, 4, null, protoLogParam0, protoLogParam1" +
"\\); \\}"))
}
diff --git a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
index 6cde7a72db53..82aa93da613b 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/SourceTransformerTest.kt
@@ -76,7 +76,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -86,7 +86,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, "test %d %f " + "abc %s\n test", protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -98,8 +98,8 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); } /* ProtoLog.w(TEST_GROUP, "test %d %f", 100, 0.1); */ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, "test %d %f", protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -109,7 +109,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, (Object[]) null); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { org.example.ProtoLogImpl.w(TEST_GROUP, 3218600869538902408L, 0, "test", (Object[]) null); }
}
}
""".trimIndent()
@@ -119,7 +119,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, protoLogParam0, protoLogParam1); }
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; org.example.ProtoLogImpl.w(TEST_GROUP, -1473209266730422156L, 9, null, protoLogParam0, protoLogParam1); }
}
}
""".trimIndent()
@@ -129,7 +129,7 @@ class SourceTransformerTest {
class Test {
void test() {
- if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, protoLogParam0, protoLogParam1, protoLogParam2);
+ if (org.example.ProtoLogImpl.Cache.TEST_GROUP_enabled[3]) { long protoLogParam0 = 100; double protoLogParam1 = 0.1; String protoLogParam2 = String.valueOf("test"); org.example.ProtoLogImpl.w(TEST_GROUP, -4447034859795564700L, 9, null, protoLogParam0, protoLogParam1, protoLogParam2);
}
}
@@ -172,12 +172,13 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_TEXT_ENABLED, out)
}
@@ -213,12 +214,13 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(3)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("\"test %d %f\"", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_MULTICALL_TEXT, out)
}
@@ -250,13 +252,13 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
- assertEquals("protoLogParam2", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_ENABLED, out)
}
@@ -287,7 +289,7 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(4, methodCall.arguments.size)
+ assertEquals(5, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("3218600869538902408L", methodCall.arguments[1].toString())
assertEquals(0.toString(), methodCall.arguments[2].toString())
@@ -321,12 +323,13 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(5, methodCall.arguments.size)
+ assertEquals(6, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-1473209266730422156L", methodCall.arguments[1].toString())
assertEquals(0b1001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
assertEquals(TRANSFORMED_CODE_TEXT_DISABLED, out)
}
@@ -358,13 +361,14 @@ class SourceTransformerTest {
Truth.assertThat(protoLogCalls).hasSize(1)
val methodCall = protoLogCalls[0] as MethodCallExpr
assertEquals("w", methodCall.name.asString())
- assertEquals(6, methodCall.arguments.size)
+ assertEquals(7, methodCall.arguments.size)
assertEquals("TEST_GROUP", methodCall.arguments[0].toString())
assertEquals("-4447034859795564700L", methodCall.arguments[1].toString())
assertEquals(0b001001.toString(), methodCall.arguments[2].toString())
- assertEquals("protoLogParam0", methodCall.arguments[3].toString())
- assertEquals("protoLogParam1", methodCall.arguments[4].toString())
- assertEquals("protoLogParam2", methodCall.arguments[5].toString())
+ assertEquals("null", methodCall.arguments[3].toString())
+ assertEquals("protoLogParam0", methodCall.arguments[4].toString())
+ assertEquals("protoLogParam1", methodCall.arguments[5].toString())
+ assertEquals("protoLogParam2", methodCall.arguments[6].toString())
assertEquals(TRANSFORMED_CODE_MULTILINE_TEXT_DISABLED, out)
}
}