Merge "Changed output of get-main-user." into udc-dev
diff --git a/cmds/gpu_counter_producer/Android.bp b/cmds/gpu_counter_producer/Android.bp
new file mode 100644
index 0000000..5b118ce
--- /dev/null
+++ b/cmds/gpu_counter_producer/Android.bp
@@ -0,0 +1,26 @@
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+cc_binary {
+ name: "gpu_counter_producer",
+
+ srcs: ["main.cpp"],
+
+ shared_libs: [
+ "libdl",
+ "liblog",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ "-fPIE",
+ "-pie",
+ ],
+
+ soc_specific: true,
+}
diff --git a/cmds/gpu_counter_producer/OWNERS b/cmds/gpu_counter_producer/OWNERS
new file mode 100644
index 0000000..892c2fb
--- /dev/null
+++ b/cmds/gpu_counter_producer/OWNERS
@@ -0,0 +1 @@
+pmuetschard@google.com
diff --git a/cmds/gpu_counter_producer/main.cpp b/cmds/gpu_counter_producer/main.cpp
new file mode 100644
index 0000000..1054cba
--- /dev/null
+++ b/cmds/gpu_counter_producer/main.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "gpu_counters"
+
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _LOG(level, msg, ...) \
+ do { \
+ fprintf(stderr, #level ": " msg "\n", ##__VA_ARGS__); \
+ ALOG##level(msg, ##__VA_ARGS__); \
+ } while (false)
+
+#define LOG_ERR(msg, ...) _LOG(E, msg, ##__VA_ARGS__)
+#define LOG_WARN(msg, ...) _LOG(W, msg, ##__VA_ARGS__)
+#define LOG_INFO(msg, ...) _LOG(I, msg, ##__VA_ARGS__)
+
+#define NELEM(x) (sizeof(x) / sizeof(x[0]))
+
+typedef void (*FN_PTR)(void);
+
+const char* kProducerPaths[] = {
+ "libgpudataproducer.so",
+};
+const char* kPidFileName = "/data/local/tmp/gpu_counter_producer.pid";
+
+static FN_PTR loadLibrary(const char* lib) {
+ char* error;
+
+ LOG_INFO("Trying %s", lib);
+ void* handle = dlopen(lib, RTLD_GLOBAL);
+ if ((error = dlerror()) != nullptr || handle == nullptr) {
+ LOG_WARN("Error loading lib: %s", error);
+ return nullptr;
+ }
+
+ FN_PTR startFunc = (FN_PTR)dlsym(handle, "start");
+ if ((error = dlerror()) != nullptr) {
+ LOG_ERR("Error looking for start symbol: %s", error);
+ dlclose(handle);
+ return nullptr;
+ }
+ return startFunc;
+}
+
+static void killExistingProcess() {
+ int fd = open(kPidFileName, O_RDONLY);
+ if (fd == -1) {
+ return;
+ }
+ char pidString[10];
+ if (read(fd, pidString, 10) > 0) {
+ int pid = -1;
+ sscanf(pidString, "%d", &pid);
+ if (pid > 0) {
+ kill(pid, SIGINT);
+ }
+ }
+ close(fd);
+}
+
+static bool writeToPidFile() {
+ killExistingProcess();
+ int fd = open(kPidFileName, O_CREAT | O_WRONLY | O_TRUNC,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ if (fd == -1) {
+ return false;
+ }
+ pid_t pid = getpid();
+ char pidString[10];
+ sprintf(pidString, "%d", pid);
+ write(fd, pidString, strlen(pidString));
+ close(fd);
+ return true;
+}
+
+static void clearPidFile() {
+ unlink(kPidFileName);
+}
+
+static void usage(const char* pname) {
+ fprintf(stderr,
+ "Starts the GPU hardware counter profiling Perfetto data producer.\n\n"
+ "usage: %s [-hf]\n"
+ " -f: run in the foreground.\n"
+ " -h: this message.\n",
+ pname);
+}
+
+// Program to load the GPU Perfetto producer .so and call start().
+int main(int argc, char** argv) {
+ const char* pname = argv[0];
+ bool foreground = false;
+ int c;
+ while ((c = getopt(argc, argv, "fh")) != -1) {
+ switch (c) {
+ case 'f':
+ foreground = true;
+ break;
+ case '?':
+ case ':':
+ case 'h':
+ usage(pname);
+ return 1;
+ }
+ }
+
+ if (optind < argc) {
+ usage(pname);
+ return 1;
+ }
+
+ if (!foreground) {
+ daemon(0, 0);
+ }
+
+ if (!writeToPidFile()) {
+ LOG_ERR("Could not open %s", kPidFileName);
+ return 1;
+ }
+
+ dlerror(); // Clear any possibly ignored previous error.
+ FN_PTR startFunc = nullptr;
+ for (int i = 0; startFunc == nullptr && i < NELEM(kProducerPaths); i++) {
+ startFunc = loadLibrary(kProducerPaths[i]);
+ }
+
+ if (startFunc == nullptr) {
+ LOG_ERR("Did not find the producer library");
+ LOG_ERR("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
+ clearPidFile();
+ return 1;
+ }
+
+ LOG_INFO("Calling start at %p", startFunc);
+ (*startFunc)();
+ LOG_WARN("Producer has exited.");
+
+ clearPidFile();
+ return 0;
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 4332d21..b92295a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4507,7 +4507,6 @@
method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
method public final void runOnUiThread(Runnable);
method public void setActionBar(@Nullable android.widget.Toolbar);
- method public void setAllowCrossUidActivitySwitchFromBelow(boolean);
method public void setContentTransitionManager(android.transition.TransitionManager);
method public void setContentView(@LayoutRes int);
method public void setContentView(android.view.View);
@@ -13645,10 +13644,10 @@
}
public final class CredentialDescription implements android.os.Parcelable {
- ctor public CredentialDescription(@NonNull String, @NonNull String, @NonNull java.util.List<android.service.credentials.CredentialEntry>);
+ ctor public CredentialDescription(@NonNull String, @NonNull java.util.Set<java.lang.String>, @NonNull java.util.List<android.service.credentials.CredentialEntry>);
method public int describeContents();
method @NonNull public java.util.List<android.service.credentials.CredentialEntry> getCredentialEntries();
- method @NonNull public String getFlattenedRequestString();
+ method @NonNull public java.util.Set<java.lang.String> getSupportedElementKeys();
method @NonNull public String getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialDescription> CREATOR;
@@ -13675,7 +13674,7 @@
method public boolean isSystemProviderRequired();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CredentialOption> CREATOR;
- field public static final String FLATTENED_REQUEST = "android.credentials.GetCredentialOption.FLATTENED_REQUEST_STRING";
+ field public static final String SUPPORTED_ELEMENT_KEYS = "android.credentials.GetCredentialOption.SUPPORTED_ELEMENT_KEYS";
}
public static final class CredentialOption.Builder {
@@ -19894,8 +19893,8 @@
method public int bulkTransfer(android.hardware.usb.UsbEndpoint, byte[], int, int, int);
method public boolean claimInterface(android.hardware.usb.UsbInterface, boolean);
method public void close();
- method public int controlTransfer(int, int, int, int, byte[], int, int);
- method public int controlTransfer(int, int, int, int, byte[], int, int, int);
+ method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int);
+ method public int controlTransfer(int, int, int, int, @Nullable byte[], int, int, int);
method public int getFileDescriptor();
method public byte[] getRawDescriptors();
method public String getSerial();
@@ -40720,7 +40719,6 @@
method public abstract void onBeginGetCredential(@NonNull android.service.credentials.BeginGetCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialResponse,android.credentials.GetCredentialException>);
method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
method public abstract void onClearCredentialState(@NonNull android.service.credentials.ClearCredentialStateRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- field @Deprecated public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_REQUEST";
field public static final String EXTRA_BEGIN_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.BEGIN_GET_CREDENTIAL_RESPONSE";
field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7107bf7..e93467b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -415,6 +415,7 @@
method public void clickNotification(@Nullable String, int, int, boolean);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
method public void expandNotificationsPanel();
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public int getLastSystemKey();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void handleSystemKey(int);
method public void sendNotificationFeedback(@Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setExpansionDisabledForSimNetworkLock(boolean);
@@ -2628,6 +2629,7 @@
field public static final String SELECTED_SPELL_CHECKER_SUBTYPE = "selected_spell_checker_subtype";
field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option";
field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
+ field public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds";
field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service";
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 125e727..8021ce0 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -9209,6 +9209,7 @@
*
* @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to
* the default behaviour
+ * @hide
*/
public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) {
ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 682fec8..5bedc9d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -240,6 +240,7 @@
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.net.InetAddress;
+import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@@ -4352,18 +4353,20 @@
static void handleAttachStartupAgents(String dataDir) {
try {
- Path code_cache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
- if (!Files.exists(code_cache)) {
+ Path codeCache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
+ if (!Files.exists(codeCache)) {
return;
}
- Path startup_path = code_cache.resolve("startup_agents");
- if (Files.exists(startup_path)) {
- for (Path p : Files.newDirectoryStream(startup_path)) {
- handleAttachAgent(
- p.toAbsolutePath().toString()
- + "="
- + dataDir,
- null);
+ Path startupPath = codeCache.resolve("startup_agents");
+ if (Files.exists(startupPath)) {
+ try (DirectoryStream<Path> startupFiles = Files.newDirectoryStream(startupPath)) {
+ for (Path p : startupFiles) {
+ handleAttachAgent(
+ p.toAbsolutePath().toString()
+ + "="
+ + dataDir,
+ null);
+ }
}
}
} catch (Exception e) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index f74be22..29f774c 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -752,6 +752,29 @@
}
/**
+ * Gets the last handled system key. A system key is a KeyEvent that the
+ * {@link com.android.server.policy.PhoneWindowManager} sends directly to the
+ * status bar, rather than forwarding to apps. If a key has never been sent to the
+ * status bar, will return -1.
+ *
+ * @return the keycode of the last KeyEvent that has been sent to the system.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR)
+ @TestApi
+ public int getLastSystemKey() {
+ try {
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ return svc.getLastSystemKey();
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ return -1;
+ }
+
+ /**
* Expand the settings panel.
*
* @hide
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 540342b..4d55fee 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -858,6 +858,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -893,6 +895,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1147,6 +1151,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1176,6 +1182,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1214,6 +1222,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1247,6 +1257,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1287,6 +1299,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1314,6 +1328,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
@@ -1458,6 +1474,8 @@
* (some versions of T may throw a {@code SecurityException}).</li>
* <li>From version U, this method should not be used
* and will always throw a @code SecurityException}.</li>
+ * <li> Apps with {@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
+ * can still access the real wallpaper on all versions. </li>
* </ul>
* <br>
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index a7aba21..b967ca9 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -32,15 +32,12 @@
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
-import android.companion.virtual.camera.VirtualCameraDevice;
-import android.companion.virtual.camera.VirtualCameraInput;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Point;
-import android.hardware.camera2.CameraCharacteristics;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.VirtualDisplayFlag;
import android.hardware.display.DisplayManagerGlobal;
@@ -329,7 +326,7 @@
}
/**
- * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
+ * A virtual device has its own virtual display, audio output, microphone, sensors, etc. The
* creator of a virtual device can take the output from the virtual display and stream it over
* to another device, and inject input events that are received from the remote device.
*
@@ -408,8 +405,6 @@
}
};
@Nullable
- private VirtualCameraDevice mVirtualCameraDevice;
- @Nullable
private VirtualAudioDevice mVirtualAudioDevice;
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@@ -603,10 +598,6 @@
mVirtualAudioDevice.close();
mVirtualAudioDevice = null;
}
- if (mVirtualCameraDevice != null) {
- mVirtualCameraDevice.close();
- mVirtualCameraDevice = null;
- }
}
/**
@@ -819,34 +810,6 @@
}
/**
- * Creates a new virtual camera. If a virtual camera was already created, it will be closed.
- *
- * @param cameraName name of the virtual camera.
- * @param characteristics camera characteristics.
- * @param virtualCameraInput callback that provides input to camera.
- * @param executor Executor on which camera input will be sent into system. Don't
- * use the Main Thread for this executor.
- * @return newly created camera;
- *
- * @hide
- */
- @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
- @NonNull
- public VirtualCameraDevice createVirtualCameraDevice(
- @NonNull String cameraName,
- @NonNull CameraCharacteristics characteristics,
- @NonNull VirtualCameraInput virtualCameraInput,
- @NonNull Executor executor) {
- if (mVirtualCameraDevice != null) {
- mVirtualCameraDevice.close();
- }
- int deviceId = getDeviceId();
- mVirtualCameraDevice = new VirtualCameraDevice(
- deviceId, cameraName, characteristics, virtualCameraInput, executor);
- return mVirtualCameraDevice;
- }
-
- /**
* Sets the visibility of the pointer icon for this VirtualDevice's associated displays.
*
* @param showPointerIcon True if the pointer should be shown; false otherwise. The default
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java b/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
deleted file mode 100644
index a7eba87..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraDevice.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.hardware.camera2.CameraCharacteristics;
-
-import androidx.annotation.NonNull;
-
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Virtual camera that is used to send image data into system.
- *
- * @hide
- */
-public final class VirtualCameraDevice implements AutoCloseable {
-
- @NonNull
- private final String mCameraDeviceName;
- @NonNull
- private final CameraCharacteristics mCameraCharacteristics;
- @NonNull
- private final VirtualCameraOutput mCameraOutput;
- private boolean mCameraRegistered = false;
-
- /**
- * VirtualCamera device constructor.
- *
- * @param virtualDeviceId ID of virtual device to which camera will be added.
- * @param cameraName must be unique for each camera per virtual device.
- * @param characteristics of camera that will be passed into system in order to describe
- * camera.
- * @param virtualCameraInput component that provides image data.
- * @param executor on which to collect image data and pass it into system.
- */
- public VirtualCameraDevice(int virtualDeviceId, @NonNull String cameraName,
- @NonNull CameraCharacteristics characteristics,
- @NonNull VirtualCameraInput virtualCameraInput, @NonNull Executor executor) {
- Objects.requireNonNull(cameraName);
- mCameraCharacteristics = Objects.requireNonNull(characteristics);
- mCameraDeviceName = generateCameraDeviceName(virtualDeviceId, cameraName);
- mCameraOutput = new VirtualCameraOutput(virtualCameraInput, executor);
- registerCamera();
- }
-
- private static String generateCameraDeviceName(int deviceId, @NonNull String cameraName) {
- return String.format(Locale.ENGLISH, "%d_%s", deviceId, Objects.requireNonNull(cameraName));
- }
-
- @Override
- public void close() {
- if (!mCameraRegistered) {
- return;
- }
-
- mCameraOutput.closeStream();
- }
-
- private void registerCamera() {
- if (mCameraRegistered) {
- return;
- }
-
- mCameraRegistered = true;
- }
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraInput.java b/core/java/android/companion/virtual/camera/VirtualCameraInput.java
deleted file mode 100644
index 690a64b..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraInput.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.hardware.camera2.params.InputConfiguration;
-
-import java.io.InputStream;
-
-/***
- * Used for sending image data into virtual camera.
- * <p>
- * The system will call {@link #openStream(InputConfiguration)} to signal when you
- * should start sending Camera image data.
- * When Camera is no longer needed, or there is change in configuration
- * {@link #closeStream()} will be called. At that time finish sending current
- * image data and then close the stream.
- * <p>
- * If Camera image data is needed again, {@link #openStream(InputConfiguration)} will be
- * called by the system.
- *
- * @hide
- */
-public interface VirtualCameraInput {
-
- /**
- * Opens a new image stream for the provided {@link InputConfiguration}.
- *
- * @param inputConfiguration image data configuration.
- * @return image data stream.
- */
- @NonNull
- InputStream openStream(@NonNull InputConfiguration inputConfiguration);
-
- /**
- * Stop sending image data and close {@link InputStream} provided in {@link
- * #openStream(InputConfiguration)}. Do nothing if there is currently no active stream.
- */
- void closeStream();
-}
diff --git a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java b/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
deleted file mode 100644
index fa1c3ad..0000000
--- a/core/java/android/companion/virtual/camera/VirtualCameraOutput.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Component for providing Camera data to the system.
- * <p>
- * {@link #getStreamDescriptor(InputConfiguration)} will be called by the system when Camera should
- * start sending image data. Camera data will continue to be sent into {@link ParcelFileDescriptor}
- * until {@link #closeStream()} is called by the system, at which point {@link ParcelFileDescriptor}
- * will be closed.
- *
- * @hide
- */
-@VisibleForTesting
-public class VirtualCameraOutput {
-
- private static final String TAG = "VirtualCameraDeviceImpl";
-
- @NonNull
- private final VirtualCameraInput mVirtualCameraInput;
- @NonNull
- private final Executor mExecutor;
- @Nullable
- private VirtualCameraStream mCameraStream;
-
- @VisibleForTesting
- public VirtualCameraOutput(@NonNull VirtualCameraInput cameraInput,
- @NonNull Executor executor) {
- mVirtualCameraInput = Objects.requireNonNull(cameraInput);
- mExecutor = Objects.requireNonNull(executor);
- }
-
- /**
- * Get a read Descriptor on which Camera HAL will receive data. At any point in time there can
- * exist a maximum of one active {@link ParcelFileDescriptor}.
- * Calling this method with a different {@link InputConfiguration} is going to close the
- * previously created file descriptor.
- *
- * @param imageConfiguration for which to create the {@link ParcelFileDescriptor}.
- * @return Newly created ParcelFileDescriptor if stream param is different from previous or if
- * this is first time call. Will return null if there was an error during Descriptor
- * creation process.
- */
- @Nullable
- @VisibleForTesting
- public ParcelFileDescriptor getStreamDescriptor(
- @NonNull InputConfiguration imageConfiguration) {
- Objects.requireNonNull(imageConfiguration);
-
- // Reuse same descriptor if stream is the same, otherwise create a new one.
- try {
- if (mCameraStream == null) {
- mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
- } else if (!mCameraStream.isSameConfiguration(imageConfiguration)) {
- mCameraStream.close();
- mCameraStream = new VirtualCameraStream(imageConfiguration, mExecutor);
- }
- } catch (IOException exception) {
- Log.e(TAG, "Unable to open file descriptor.", exception);
- return null;
- }
-
- InputStream imageStream = mVirtualCameraInput.openStream(imageConfiguration);
- mCameraStream.startSending(imageStream);
- return mCameraStream.getDescriptor();
- }
-
- /**
- * Closes currently opened stream. If there is no stream, do nothing.
- */
- @VisibleForTesting
- public void closeStream() {
- mVirtualCameraInput.closeStream();
- if (mCameraStream != null) {
- mCameraStream.close();
- mCameraStream = null;
- }
-
- try {
- mVirtualCameraInput.closeStream();
- } catch (Exception e) {
- Log.e(TAG, "Error during closing stream.", e);
- }
- }
-
- private static class VirtualCameraStream implements AutoCloseable {
-
- private static final String TAG = "VirtualCameraStream";
- private static final int BUFFER_SIZE = 1024;
-
- private static final int SENDING_STATE_INITIAL = 0;
- private static final int SENDING_STATE_IN_PROGRESS = 1;
- private static final int SENDING_STATE_CLOSED = 2;
-
- @NonNull
- private final InputConfiguration mImageConfiguration;
- @NonNull
- private final Executor mExecutor;
- @Nullable
- private final ParcelFileDescriptor mReadDescriptor;
- @Nullable
- private final ParcelFileDescriptor mWriteDescriptor;
- private int mSendingState;
-
- VirtualCameraStream(@NonNull InputConfiguration imageConfiguration,
- @NonNull Executor executor) throws IOException {
- mSendingState = SENDING_STATE_INITIAL;
- mImageConfiguration = Objects.requireNonNull(imageConfiguration);
- mExecutor = Objects.requireNonNull(executor);
- ParcelFileDescriptor[] parcels = ParcelFileDescriptor.createPipe();
- mReadDescriptor = parcels[0];
- mWriteDescriptor = parcels[1];
- }
-
- boolean isSameConfiguration(@NonNull InputConfiguration imageConfiguration) {
- return mImageConfiguration == Objects.requireNonNull(imageConfiguration);
- }
-
- @Nullable
- ParcelFileDescriptor getDescriptor() {
- return mReadDescriptor;
- }
-
- public void startSending(@NonNull InputStream inputStream) {
- Objects.requireNonNull(inputStream);
-
- if (mSendingState != SENDING_STATE_INITIAL) {
- return;
- }
-
- mSendingState = SENDING_STATE_IN_PROGRESS;
- mExecutor.execute(() -> sendData(inputStream));
- }
-
- @Override
- public void close() {
- mSendingState = SENDING_STATE_CLOSED;
- try {
- mReadDescriptor.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close read descriptor.", e);
- }
- try {
- mWriteDescriptor.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close write descriptor.", e);
- }
- }
-
- private void sendData(@NonNull InputStream inputStream) {
- Objects.requireNonNull(inputStream);
-
- byte[] buffer = new byte[BUFFER_SIZE];
- FileDescriptor fd = mWriteDescriptor.getFileDescriptor();
- try (FileOutputStream outputStream = new FileOutputStream(fd)) {
- while (mSendingState == SENDING_STATE_IN_PROGRESS) {
- int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
- if (bytesRead < 1) continue;
-
- outputStream.write(buffer, 0, bytesRead);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error while sending camera data.", e);
- }
- }
- }
-}
diff --git a/core/java/android/credentials/CredentialDescription.java b/core/java/android/credentials/CredentialDescription.java
index a23d7e4..db71624 100644
--- a/core/java/android/credentials/CredentialDescription.java
+++ b/core/java/android/credentials/CredentialDescription.java
@@ -25,8 +25,10 @@
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Represents the type and contained data fields of a {@link Credential}.
@@ -42,10 +44,10 @@
private final String mType;
/**
- * Flattened semicolon separated keys of JSON values to match with requests.
+ * Keys of elements to match with Credential requests.
*/
@NonNull
- private final String mFlattenedRequestString;
+ private final Set<String> mSupportedElementKeys;
/**
* The credential entries to be used in the UI.
@@ -57,8 +59,7 @@
* Constructs a {@link CredentialDescription}.
*
* @param type the type of the credential returned.
- * @param flattenedRequestString flattened semicolon separated keys of JSON values
- * to match with requests.
+ * @param supportedElementKeys Keys of elements to match with Credential requests.
* @param credentialEntries a list of {@link CredentialEntry}s that are to be shown on the
* account selector if a credential matches with this description.
* Each entry contains information to be displayed within an
@@ -68,10 +69,10 @@
* @throws IllegalArgumentException If type is empty.
*/
public CredentialDescription(@NonNull String type,
- @NonNull String flattenedRequestString,
+ @NonNull Set<String> supportedElementKeys,
@NonNull List<CredentialEntry> credentialEntries) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
- mFlattenedRequestString = Preconditions.checkStringNotEmpty(flattenedRequestString);
+ mSupportedElementKeys = Objects.requireNonNull(supportedElementKeys);
mCredentialEntries = Objects.requireNonNull(credentialEntries);
Preconditions.checkArgument(credentialEntries.size()
<= MAX_ALLOWED_ENTRIES_PER_DESCRIPTION,
@@ -82,15 +83,15 @@
private CredentialDescription(@NonNull Parcel in) {
String type = in.readString8();
- String flattenedRequestString = in.readString();
+ List<String> descriptions = in.createStringArrayList();
List<CredentialEntry> entries = new ArrayList<>();
in.readTypedList(entries, CredentialEntry.CREATOR);
mType = type;
AnnotationValidations.validate(android.annotation.NonNull.class, null, mType);
- mFlattenedRequestString = flattenedRequestString;
+ mSupportedElementKeys = new HashSet<>(descriptions);
AnnotationValidations.validate(android.annotation.NonNull.class, null,
- mFlattenedRequestString);
+ mSupportedElementKeys);
mCredentialEntries = entries;
AnnotationValidations.validate(android.annotation.NonNull.class, null,
mCredentialEntries);
@@ -125,7 +126,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
- dest.writeString(mFlattenedRequestString);
+ dest.writeStringList(mSupportedElementKeys.stream().toList());
dest.writeTypedList(mCredentialEntries, flags);
}
@@ -141,8 +142,8 @@
* Returns the flattened JSON string that will be matched with requests.
*/
@NonNull
- public String getFlattenedRequestString() {
- return mFlattenedRequestString;
+ public Set<String> getSupportedElementKeys() {
+ return new HashSet<>(mSupportedElementKeys);
}
/**
@@ -155,18 +156,18 @@
/**
* {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mFlattenedRequestString} are enough for hashing. Constructor
+ * {@link CredentialDescription#mSupportedElementKeys} are enough for hashing. Constructor
* enforces {@link CredentialEntry} to have the same type and
* {@link android.app.slice.Slice} contained by the entry can not be hashed.
*/
@Override
public int hashCode() {
- return Objects.hash(mType, mFlattenedRequestString);
+ return Objects.hash(mType, mSupportedElementKeys);
}
/**
* {@link CredentialDescription#mType} and
- * {@link CredentialDescription#mFlattenedRequestString} are enough for equality check.
+ * {@link CredentialDescription#mSupportedElementKeys} are enough for equality check.
*/
@Override
public boolean equals(Object obj) {
@@ -175,6 +176,6 @@
}
CredentialDescription other = (CredentialDescription) obj;
return mType.equals(other.mType)
- && mFlattenedRequestString.equals(other.mFlattenedRequestString);
+ && mSupportedElementKeys.equals(other.mSupportedElementKeys);
}
}
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index da6656a..e933123 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -43,12 +43,12 @@
public final class CredentialOption implements Parcelable {
/**
- * Bundle key to the flattened version of the JSON request string. Framework will use this key
+ * Bundle key to the list of elements keys supported/requested. Framework will use this key
* to determine which types of Credentials will utilize Credential Registry when filtering
* Credential Providers to ping.
*/
- public static final String FLATTENED_REQUEST = "android.credentials"
- + ".GetCredentialOption.FLATTENED_REQUEST_STRING";
+ public static final String SUPPORTED_ELEMENT_KEYS = "android.credentials"
+ + ".GetCredentialOption.SUPPORTED_ELEMENT_KEYS";
/**
* The requested credential type.
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index b8b1eaa..312bfdf 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -16,6 +16,7 @@
package android.hardware;
import android.annotation.IntDef;
+import android.view.SurfaceControl;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -376,12 +377,19 @@
*/
public static final int RANGE_LIMITED = 2 << 27;
/**
- * Extended range is used for scRGB only.
+ * Extended range can be used in combination with FP16 to communicate scRGB or with
+ * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+ * to indicate an HDR range.
*
- * <p>Intended for use with floating point pixel formats. [0.0 - 1.0] is the standard
- * sRGB space. Values outside the range [0.0 - 1.0] can encode
- * color outside the sRGB gamut. [-0.5, 7.5] is the scRGB range.
+ * <p>When used with floating point pixel formats and #STANDARD_BT709 then [0.0 - 1.0] is the
+ * standard sRGB space and values outside the range [0.0 - 1.0] can encode
+ * color outside the sRGB gamut. [-0.5, 7.5] is the standard scRGB range.
* Used to blend/merge multiple dataspaces on a single display.</p>
+ *
+ * <p>As of {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} this may be combined with
+ * {@link android.view.SurfaceControl.Transaction#setExtendedRangeBrightness(SurfaceControl, float, float)}
+ * and other formats such as {@link HardwareBuffer#RGBA_8888} or
+ * {@link HardwareBuffer#RGBA_1010102} to communicate a variable HDR brightness range</p>
*/
public static final int RANGE_EXTENDED = 3 << 27;
diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java
index f4ee9a2..e2568e3 100644
--- a/core/java/android/hardware/input/InputDeviceLightsManager.java
+++ b/core/java/android/hardware/input/InputDeviceLightsManager.java
@@ -18,7 +18,6 @@
import android.annotation.NonNull;
import android.app.ActivityThread;
-import android.content.Context;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
@@ -44,8 +43,7 @@
// Package name
private final String mPackageName;
- InputDeviceLightsManager(Context context, int deviceId) {
- super(context);
+ InputDeviceLightsManager(int deviceId) {
mGlobal = InputManagerGlobal.getInstance();
mDeviceId = deviceId;
mPackageName = ActivityThread.currentPackageName();
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8a4a0e4..e7385b6 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -32,8 +32,6 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.hardware.BatteryState;
-import android.hardware.SensorManager;
-import android.hardware.lights.LightsManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -41,7 +39,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Vibrator;
-import android.os.VibratorManager;
import android.util.Log;
import android.view.Display;
import android.view.InputDevice;
@@ -1311,47 +1308,6 @@
}
/**
- * Gets a vibrator manager service associated with an input device, always creates a new
- * instance.
- * @return The vibrator manager, never null.
- * @hide
- */
- @NonNull
- public VibratorManager getInputDeviceVibratorManager(int deviceId) {
- return new InputDeviceVibratorManager(deviceId);
- }
-
- /**
- * Gets a sensor manager service associated with an input device, always creates a new instance.
- * @return The sensor manager, never null.
- * @hide
- */
- @NonNull
- public SensorManager getInputDeviceSensorManager(int deviceId) {
- return mGlobal.getInputDeviceSensorManager(deviceId);
- }
-
- /**
- * Gets a battery state object associated with an input device, assuming it has one.
- * @return The battery, never null.
- * @hide
- */
- @NonNull
- public BatteryState getInputDeviceBatteryState(int deviceId, boolean hasBattery) {
- return mGlobal.getInputDeviceBatteryState(deviceId, hasBattery);
- }
-
- /**
- * Gets a lights manager associated with an input device, always creates a new instance.
- * @return The lights manager, never null.
- * @hide
- */
- @NonNull
- public LightsManager getInputDeviceLightsManager(int deviceId) {
- return new InputDeviceLightsManager(getContext(), deviceId);
- }
-
- /**
* Cancel all ongoing pointer gestures on all displays.
* @hide
*/
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 31ce7d9..701980d 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -29,6 +29,7 @@
import android.hardware.input.InputManager.OnTabletModeChangedListener;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
import android.hardware.lights.LightsRequest;
import android.os.Binder;
import android.os.CombinedVibration;
@@ -876,7 +877,7 @@
}
/**
- * @see InputManager#getInputDeviceSensorManager(int)
+ * @see InputDevice#getSensorManager()
*/
@NonNull
public SensorManager getInputDeviceSensorManager(int deviceId) {
@@ -955,6 +956,14 @@
}
/**
+ * @see InputDevice#getLightsManager()
+ */
+ @NonNull
+ public LightsManager getInputDeviceLightsManager(int deviceId) {
+ return new InputDeviceLightsManager(deviceId);
+ }
+
+ /**
* Gets a list of light objects associated with an input device.
* @return The list of lights, never null.
*/
@@ -1032,7 +1041,7 @@
}
/**
- * @see InputManager#getInputDeviceVibratorManager(int)
+ * @see InputDevice#getVibratorManager()
*/
@NonNull
public VibratorManager getInputDeviceVibratorManager(int deviceId) {
@@ -1138,7 +1147,7 @@
}
/**
- * @see InputManager#getKeyCodeforKeyLocation(int, int)
+ * @see InputManager#getKeyCodeForKeyLocation(int, int)
*/
public int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode) {
try {
diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java
index 2d9bc0e..b71b7e0 100644
--- a/core/java/android/hardware/lights/LightsManager.java
+++ b/core/java/android/hardware/lights/LightsManager.java
@@ -25,8 +25,6 @@
import android.os.Binder;
import android.os.IBinder;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
@@ -39,7 +37,6 @@
public abstract class LightsManager {
private static final String TAG = "LightsManager";
- @NonNull private final Context mContext;
// These enum values copy the values from {@link com.android.server.lights.LightsManager}
// and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light
// and following types are available through this API.
@@ -62,9 +59,7 @@
/**
* @hide to prevent subclassing from outside of the framework
*/
- public LightsManager(Context context) {
- mContext = Preconditions.checkNotNull(context);
- }
+ public LightsManager() {}
/**
* Returns the lights available on the device.
diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java
index 055a7f4..3beb4ba 100644
--- a/core/java/android/hardware/lights/SystemLightsManager.java
+++ b/core/java/android/hardware/lights/SystemLightsManager.java
@@ -59,7 +59,6 @@
*/
@VisibleForTesting
public SystemLightsManager(@NonNull Context context, @NonNull ILightsManager service) {
- super(context);
mService = Preconditions.checkNotNull(service);
}
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 7c2e518..44144d9 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -238,7 +238,7 @@
* or negative value for failure
*/
public int controlTransfer(int requestType, int request, int value,
- int index, byte[] buffer, int length, int timeout) {
+ int index, @Nullable byte[] buffer, int length, int timeout) {
return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
}
@@ -263,7 +263,7 @@
* or negative value for failure
*/
public int controlTransfer(int requestType, int request, int value, int index,
- byte[] buffer, int offset, int length, int timeout) {
+ @Nullable byte[] buffer, int offset, int length, int timeout) {
checkBounds(buffer, offset, length);
return native_control_request(requestType, request, value, index,
buffer, offset, length, timeout);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 5137bc1..7ae280f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7414,6 +7414,8 @@
*
* @hide
*/
+ @TestApi
+ @Readable
@SuppressLint("NoSettingsProvider")
public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled";
@@ -11728,13 +11730,14 @@
public static final String THEATER_MODE_ON = "theater_mode_on";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Bluetooth radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Bluetooth
+ * radio.
*/
@Readable
public static final String RADIO_BLUETOOTH = "bluetooth";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify Wi-Fi radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify Wi-Fi radio.
*/
@Readable
public static final String RADIO_WIFI = "wifi";
@@ -11751,12 +11754,40 @@
public static final String RADIO_CELL = "cell";
/**
- * Constant for use in AIRPLANE_MODE_RADIOS to specify NFC radio.
+ * Constant for use in AIRPLANE_MODE_RADIOS or SATELLITE_MODE_RADIOS to specify NFC radio.
*/
@Readable
public static final String RADIO_NFC = "nfc";
/**
+ * Constant for use in SATELLITE_MODE_RADIOS to specify UWB radio.
+ *
+ * {@hide}
+ */
+ public static final String RADIO_UWB = "uwb";
+
+
+ /**
+ * A comma separated list of radios that need to be disabled when satellite mode is on.
+ *
+ * {@hide}
+ */
+ public static final String SATELLITE_MODE_RADIOS = "satellite_mode_radios";
+
+ /**
+ * The satellite mode is enabled for the user. When the satellite mode is enabled, the
+ * satellite radio will be turned on and all other radios will be turned off. When the
+ * satellite mode is disabled, the satellite radio will be turned off and the states of
+ * other radios will be restored.
+ * <p>
+ * When this setting is set to 0, it means the satellite mode is disabled. When this
+ * setting is set to 1, it means the satellite mode is enabled.
+ *
+ * {@hide}
+ */
+ public static final String SATELLITE_MODE_ENABLED = "satellite_mode_enabled";
+
+ /**
* A comma separated list of radios that need to be disabled when airplane mode
* is on. This overrides WIFI_ON and BLUETOOTH_ON, if Wi-Fi and bluetooth are
* included in the comma separated list.
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index 47b75d1..9120b8a 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -224,14 +224,6 @@
Log.e(TAG, "Failed to get XML metadata", e);
}
- // 5. Extract the legacy metadata.
- try {
- builder.addCapabilities(
- populateLegacyProviderCapabilities(resources, metadata, serviceInfo));
- } catch (Exception e) {
- Log.e(TAG, "Failed to get legacy metadata ", e);
- }
-
return builder;
}
@@ -325,38 +317,6 @@
return capabilities;
}
- private static Set<String> populateLegacyProviderCapabilities(
- Resources resources, Bundle metadata, ServiceInfo serviceInfo) {
- Set<String> output = new HashSet<>();
- Set<String> capabilities = new HashSet<>();
-
- try {
- String[] discovered =
- resources.getStringArray(
- metadata.getInt(CredentialProviderService.CAPABILITY_META_DATA_KEY));
- if (discovered != null) {
- capabilities.addAll(Arrays.asList(discovered));
- }
- } catch (Resources.NotFoundException | NullPointerException e) {
- Log.e(TAG, "Failed to get capabilities: ", e);
- }
-
- if (capabilities.size() == 0) {
- Log.e(TAG, "No capabilities found for provider:" + serviceInfo);
- return output;
- }
-
- for (String capability : capabilities) {
- if (capability == null || capability.isEmpty()) {
- Log.w(TAG, "Skipping empty/null capability");
- continue;
- }
- Log.i(TAG, "Capabilities found for provider: " + capability);
- output.add(capability);
- }
- return output;
- }
-
private static ServiceInfo getServiceInfoOrThrow(
@NonNull ComponentName serviceComponent, int userId)
throws PackageManager.NameNotFoundException {
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 6824159..b977606 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -156,14 +156,6 @@
private static final String TAG = "CredProviderService";
- /**
- * The list of capabilities exposed by a credential provider.
- *
- * @deprecated Replaced with {@link android.service.credentials#SERVICE_META_DATA}
- */
- @Deprecated
- public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
-
/**
* Name under which a Credential Provider service component publishes information
* about itself. This meta-data must reference an XML resource containing
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index e81aecb..48fb719 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -28,7 +28,6 @@
import android.hardware.SensorManager;
import android.hardware.input.HostUsiVersion;
import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
import android.hardware.lights.LightsManager;
import android.icu.util.ULocale;
@@ -1191,7 +1190,8 @@
public LightsManager getLightsManager() {
synchronized (mMotionRanges) {
if (mLightsManager == null) {
- mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId);
+ mLightsManager = InputManagerGlobal.getInstance()
+ .getInputDeviceLightsManager(mId);
}
}
return mLightsManager;
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 16bc155..bd249c4 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -63,37 +63,6 @@
*/
public class InsetsState implements Parcelable {
- /**
- * Internal representation of inset source types. This is different from the public API in
- * {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
- * at the same time.
- */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "ITYPE", value = {
- ITYPE_CAPTION_BAR,
- ITYPE_LEFT_TAPPABLE_ELEMENT,
- ITYPE_TOP_TAPPABLE_ELEMENT,
- ITYPE_RIGHT_TAPPABLE_ELEMENT,
- ITYPE_BOTTOM_TAPPABLE_ELEMENT,
- ITYPE_LEFT_GENERIC_OVERLAY,
- ITYPE_TOP_GENERIC_OVERLAY,
- ITYPE_RIGHT_GENERIC_OVERLAY,
- ITYPE_BOTTOM_GENERIC_OVERLAY
- })
- public @interface InternalInsetsType {}
-
- public static final int ITYPE_CAPTION_BAR = 0;
-
- public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 1;
- public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 2;
- public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 3;
- public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 4;
-
- public static final int ITYPE_LEFT_GENERIC_OVERLAY = 5;
- public static final int ITYPE_TOP_GENERIC_OVERLAY = 6;
- public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 7;
- public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 8;
-
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "ISIDE", value = {
ISIDE_LEFT,
@@ -677,30 +646,6 @@
&& !WindowConfiguration.inMultiWindowMode(windowingMode);
}
- /**
- * Converting a internal type to the public type.
- * @param type internal insets type, {@code InternalInsetsType}.
- * @return public insets type, {@code Type.InsetsType}.
- */
- public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
- switch (type) {
- case ITYPE_LEFT_GENERIC_OVERLAY:
- case ITYPE_TOP_GENERIC_OVERLAY:
- case ITYPE_RIGHT_GENERIC_OVERLAY:
- case ITYPE_BOTTOM_GENERIC_OVERLAY:
- return Type.SYSTEM_OVERLAYS;
- case ITYPE_CAPTION_BAR:
- return Type.CAPTION_BAR;
- case ITYPE_LEFT_TAPPABLE_ELEMENT:
- case ITYPE_TOP_TAPPABLE_ELEMENT:
- case ITYPE_RIGHT_TAPPABLE_ELEMENT:
- case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
- return Type.TAPPABLE_ELEMENT;
- default:
- throw new IllegalArgumentException("Unknown type: " + type);
- }
- }
-
public void dump(String prefix, PrintWriter pw) {
final String newPrefix = prefix + " ";
pw.println(prefix + "InsetsState");
diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java
index b74b80e..7ad43c7 100644
--- a/core/java/android/view/WindowMetrics.java
+++ b/core/java/android/view/WindowMetrics.java
@@ -162,7 +162,7 @@
return WindowMetrics.class.getSimpleName() + ":{"
+ "bounds=" + mBounds
+ ", windowInsets=" + mWindowInsets
- + ", density" + mDensity
+ + ", density=" + mDensity
+ "}";
}
}
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index e267a7f..e51eff4 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -150,6 +150,15 @@
"package_deny_list_for_unimportant_view";
/**
+ * Sets the list of activities and packages allowed for autofill. The format is same with
+ * {@link #DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW}
+ *
+ * @hide
+ */
+ public static final String DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST =
+ "package_and_activity_allowlist_for_triggering_fill_request";
+
+ /**
* Whether the heuristics check for view is enabled
*/
public static final String DEVICE_CONFIG_TRIGGER_FILL_REQUEST_ON_UNIMPORTANT_VIEW =
@@ -183,6 +192,7 @@
*/
public static final String DEVICE_CONFIG_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES =
"should_enable_autofill_on_all_view_types";
+
// END AUTOFILL FOR ALL APPS FLAGS //
@@ -378,6 +388,16 @@
DEVICE_CONFIG_PACKAGE_DENYLIST_FOR_UNIMPORTANT_VIEW, "");
}
+ /**
+ * Get autofill allowlist from flag
+ *
+ * @hide
+ */
+ public static String getAllowlistStringFromFlag() {
+ return DeviceConfig.getString(
+ DeviceConfig.NAMESPACE_AUTOFILL,
+ DEVICE_CONFIG_PACKAGE_AND_ACTIVITY_ALLOWLIST_FOR_TRIGGERING_FILL_REQUEST, "");
+ }
// START AUTOFILL PCC CLASSIFICATION FUNCTIONS
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index c5befb6..cc8ab10 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -694,7 +694,18 @@
private boolean mIsPackagePartiallyDeniedForAutofill = false;
// A deny set read from device config
- private Set<String> mDeniedActivitiySet = new ArraySet<>();
+ private Set<String> mDeniedActivitySet = new ArraySet<>();
+
+ // If a package is fully allowed, all views in package will skip the heuristic check
+ private boolean mIsPackageFullyAllowedForAutofill = false;
+
+ // If a package is partially denied, autofill manager will check whether
+ // current activity is in allowed activity set. If it's allowed activity, then autofill manager
+ // will skip the heuristic check
+ private boolean mIsPackagePartiallyAllowedForAutofill = false;
+
+ // An allowed activity set read from device config
+ private Set<String> mAllowedActivitySet = new ArraySet<>();
// Indicates whether called the showAutofillDialog() method.
private boolean mShowAutofillDialogCalled = false;
@@ -873,19 +884,34 @@
AutofillFeatureFlags.getNonAutofillableImeActionIdSetFromFlag();
final String denyListString = AutofillFeatureFlags.getDenylistStringFromFlag();
+ final String allowlistString = AutofillFeatureFlags.getAllowlistStringFromFlag();
final String packageName = mContext.getPackageName();
mIsPackageFullyDeniedForAutofill =
- isPackageFullyDeniedForAutofill(denyListString, packageName);
+ isPackageFullyAllowedOrDeniedForAutofill(denyListString, packageName);
+
+ mIsPackageFullyAllowedForAutofill =
+ isPackageFullyAllowedOrDeniedForAutofill(allowlistString, packageName);
if (!mIsPackageFullyDeniedForAutofill) {
mIsPackagePartiallyDeniedForAutofill =
- isPackagePartiallyDeniedForAutofill(denyListString, packageName);
+ isPackagePartiallyDeniedOrAllowedForAutofill(denyListString, packageName);
+ }
+
+ if (!mIsPackageFullyAllowedForAutofill) {
+ mIsPackagePartiallyAllowedForAutofill =
+ isPackagePartiallyDeniedOrAllowedForAutofill(allowlistString, packageName);
}
if (mIsPackagePartiallyDeniedForAutofill) {
- setDeniedActivitySetWithDenyList(denyListString, packageName);
+ mDeniedActivitySet = getDeniedOrAllowedActivitySetFromString(
+ denyListString, packageName);
+ }
+
+ if (mIsPackagePartiallyAllowedForAutofill) {
+ mAllowedActivitySet = getDeniedOrAllowedActivitySetFromString(
+ allowlistString, packageName);
}
}
@@ -921,59 +947,59 @@
return true;
}
- private boolean isPackageFullyDeniedForAutofill(
- @NonNull String denyListString, @NonNull String packageName) {
- // If "PackageName:;" is in the string, then it means the package name is in denylist
- // and there are no activities specified under it. That means the package is fully
- // denied for autofill
- return denyListString.indexOf(packageName + ":;") != -1;
+ private boolean isPackageFullyAllowedOrDeniedForAutofill(
+ @NonNull String listString, @NonNull String packageName) {
+ // If "PackageName:;" is in the string, then it the package is fully denied or allowed for
+ // autofill, depending on which string is passed to this function
+ return listString.indexOf(packageName + ":;") != -1;
}
- private boolean isPackagePartiallyDeniedForAutofill(
- @NonNull String denyListString, @NonNull String packageName) {
- // This check happens after checking package is not fully denied. If "PackageName:" instead
- // is in denylist, then it means there are specific activities to be denied. So the package
- // is partially denied for autofill
- return denyListString.indexOf(packageName + ":") != -1;
+ private boolean isPackagePartiallyDeniedOrAllowedForAutofill(
+ @NonNull String listString, @NonNull String packageName) {
+ // If "PackageName:" is in string when "PackageName:;" is not, then it means there are
+ // specific activities to be allowed or denied. So the package is partially allowed or
+ // denied for autofill.
+ return listString.indexOf(packageName + ":") != -1;
}
/**
- * Get the denied activitiy names under specified package from denylist and set it in field
- * mDeniedActivitiySet
+ * Get the denied or allowed activitiy names under specified package from the list string and
+ * set it in fields accordingly
*
- * If using parameter as the example below, the denied activity set would be set to
- * Set{Activity1,Activity2}.
+ * For example, if the package name is Package1, and the string is
+ * "Package1:Activity1,Activity2;", then the extracted activity set would be
+ * {Activity1, Activity2}
*
- * @param denyListString Denylist that is got from device config. For example,
+ * @param listString Denylist that is got from device config. For example,
* "Package1:Activity1,Activity2;Package2:;"
- * @param packageName Specify to extract activities under which package.For example,
- * "Package1:;"
+ * @param packageName Specify which package to extract.For example, "Package1"
+ *
+ * @return the extracted activity set, For example, {Activity1, Activity2}
*/
- private void setDeniedActivitySetWithDenyList(
- @NonNull String denyListString, @NonNull String packageName) {
+ private Set<String> getDeniedOrAllowedActivitySetFromString(
+ @NonNull String listString, @NonNull String packageName) {
// 1. Get the index of where the Package name starts
- final int packageInStringIndex = denyListString.indexOf(packageName + ":");
+ final int packageInStringIndex = listString.indexOf(packageName + ":");
// 2. Get the ";" index after this index of package
- final int firstNextSemicolonIndex = denyListString.indexOf(";", packageInStringIndex);
+ final int firstNextSemicolonIndex = listString.indexOf(";", packageInStringIndex);
// 3. Get the activity names substring between the indexes
final int activityStringStartIndex = packageInStringIndex + packageName.length() + 1;
+
if (activityStringStartIndex >= firstNextSemicolonIndex) {
- Log.e(TAG, "Failed to get denied activity names from denylist because it's wrongly "
+ Log.e(TAG, "Failed to get denied activity names from list because it's wrongly "
+ "formatted");
- return;
+ return new ArraySet<>();
}
final String activitySubstring =
- denyListString.substring(activityStringStartIndex, firstNextSemicolonIndex);
+ listString.substring(activityStringStartIndex, firstNextSemicolonIndex);
// 4. Split the activity name substring
final String[] activityStringArray = activitySubstring.split(",");
- // 5. Set the denied activity set
- mDeniedActivitiySet = new ArraySet<>(Arrays.asList(activityStringArray));
-
- return;
+ // 5. return the extracted activities in a set
+ return new ArraySet<>(Arrays.asList(activityStringArray));
}
/**
@@ -992,7 +1018,32 @@
return false;
}
final ComponentName clientActivity = client.autofillClientGetComponentName();
- if (mDeniedActivitiySet.contains(clientActivity.flattenToShortString())) {
+ if (mDeniedActivitySet.contains(clientActivity.flattenToShortString())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check whether current activity is allowlisted for autofill.
+ *
+ * If it is, the view in current activity will bypass heuristic check when checking whether it's
+ * autofillable
+ *
+ * @hide
+ */
+ public boolean isActivityAllowedForAutofill() {
+ if (mIsPackageFullyAllowedForAutofill) {
+ return true;
+ }
+ if (mIsPackagePartiallyAllowedForAutofill) {
+ final AutofillClient client = getClient();
+ if (client == null) {
+ return false;
+ }
+ final ComponentName clientActivity = client.autofillClientGetComponentName();
+ if (mAllowedActivitySet.contains(clientActivity.flattenToShortString())) {
return true;
}
}
@@ -1009,17 +1060,22 @@
* @hide
*/
public boolean isAutofillable(View view) {
- if (isActivityDeniedForAutofill()) {
- Log.d(TAG, "view is not autofillable - activity denied for autofill");
- return false;
- }
-
// Duplicate the autofill type check here because ViewGroup will call this function to
// decide whether to include view in assist structure.
// Also keep the autofill type check inside View#IsAutofillable() to serve as an early out
// or if other functions need to call it.
if (view.getAutofillType() == View.AUTOFILL_TYPE_NONE) return false;
+ if (isActivityDeniedForAutofill()) {
+ Log.d(TAG, "view is not autofillable - activity denied for autofill");
+ return false;
+ }
+
+ if (isActivityAllowedForAutofill()) {
+ Log.d(TAG, "view is autofillable - activity allowed for autofill");
+ return true;
+ }
+
if (view instanceof EditText) {
return isPassingImeActionCheck((EditText) view);
}
@@ -1037,7 +1093,7 @@
|| view instanceof RadioGroup) {
return true;
}
-
+ Log.d(TAG, "view is not autofillable - not important and filtered by view type check");
return false;
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 0bbaac0f..6523fff 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2000,8 +2000,19 @@
* in order to facilitate debugging of web layouts and JavaScript
* code running inside WebViews. Please refer to WebView documentation
* for the debugging guide.
- *
- * The default is {@code false}.
+ * <p>
+ * In WebView 113.0.5656.0 and later, this is enabled automatically if the
+ * app is declared as
+ * <a href="https://developer.android.com/guide/topics/manifest/application-element#debug">
+ * {@code android:debuggable="true"}</a> in its manifest; otherwise, the
+ * default is {@code false}.
+ * <p>
+ * Enabling web contents debugging allows the state of any WebView in the
+ * app to be inspected and modified by the user via adb. This is a security
+ * liability and should not be enabled in production builds of apps unless
+ * this is an explicitly intended use of the app. More info on
+ * <a href="https://developer.android.com/topic/security/risks/android-debuggable">
+ * secure debug settings</a>.
*
* @param enabled whether to enable web contents debugging
*/
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index 5140594..e0ee683 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -16,6 +16,8 @@
package android.window;
+import android.annotation.AnimRes;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -245,6 +247,9 @@
public static final class CustomAnimationInfo implements Parcelable {
private final String mPackageName;
private int mWindowAnimations;
+ @AnimRes private int mCustomExitAnim;
+ @AnimRes private int mCustomEnterAnim;
+ @ColorInt private int mCustomBackground;
/**
* The package name of the windowAnimations.
@@ -261,6 +266,27 @@
return mWindowAnimations;
}
+ /**
+ * The exit animation resource Id of customize activity transition.
+ */
+ public int getCustomExitAnim() {
+ return mCustomExitAnim;
+ }
+
+ /**
+ * The entering animation resource Id of customize activity transition.
+ */
+ public int getCustomEnterAnim() {
+ return mCustomEnterAnim;
+ }
+
+ /**
+ * The background color of customize activity transition.
+ */
+ public int getCustomBackground() {
+ return mCustomBackground;
+ }
+
public CustomAnimationInfo(@NonNull String packageName) {
this.mPackageName = packageName;
}
@@ -274,11 +300,17 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mPackageName);
dest.writeInt(mWindowAnimations);
+ dest.writeInt(mCustomEnterAnim);
+ dest.writeInt(mCustomExitAnim);
+ dest.writeInt(mCustomBackground);
}
private CustomAnimationInfo(@NonNull Parcel in) {
mPackageName = in.readString8();
mWindowAnimations = in.readInt();
+ mCustomEnterAnim = in.readInt();
+ mCustomExitAnim = in.readInt();
+ mCustomBackground = in.readInt();
}
@Override
@@ -349,10 +381,25 @@
* Set windowAnimations for customize animation.
*/
public Builder setWindowAnimations(String packageName, int windowAnimations) {
- mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+ if (mCustomAnimationInfo == null) {
+ mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+ }
mCustomAnimationInfo.mWindowAnimations = windowAnimations;
return this;
}
+ /**
+ * Set resources ids for customize activity animation.
+ */
+ public Builder setCustomAnimation(String packageName, @AnimRes int enterResId,
+ @AnimRes int exitResId, @ColorInt int backgroundColor) {
+ if (mCustomAnimationInfo == null) {
+ mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+ }
+ mCustomAnimationInfo.mCustomExitAnim = exitResId;
+ mCustomAnimationInfo.mCustomEnterAnim = enterResId;
+ mCustomAnimationInfo.mCustomBackground = backgroundColor;
+ return this;
+ }
/**
* Builds and returns an instance of {@link BackNavigationInfo}
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index b83d1d8..f19f6c7 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -87,6 +87,8 @@
new ComponentName("com.android.server.accessibility", "OneHandedMode");
public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME =
new ComponentName("com.android.server.accessibility", "ReduceBrightColors");
+ public static final ComponentName FONT_SIZE_COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "FontSize");
// The component name for the sub setting of Accessibility button in Accessibility settings
public static final ComponentName ACCESSIBILITY_BUTTON_COMPONENT_NAME =
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 3226669..1c0da18 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -328,6 +328,7 @@
mTracingStarted = true;
markEvent("FT#begin");
Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ markEvent("FT#layerId#" + mSurfaceControl.getLayerId());
mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
@@ -437,8 +438,10 @@
"The length of the trace event description <%s> exceeds %d",
desc, MAX_LENGTH_EVENT_DESC));
}
- Trace.beginSection(TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
- Trace.endSection();
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ Trace.instant(Trace.TRACE_TAG_APP,
+ TextUtils.formatSimple("%s#%s", mSession.getName(), desc));
+ }
}
private void notifyCujEvent(String action) {
diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
index 6a61656..096d1cd 100644
--- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
+++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java
@@ -137,14 +137,14 @@
close();
}
- /** Records the start of ActivityManagerService#dumpStackTraces. */
+ /** Records the start of StackTracesDumpHelper#dumpStackTraces. */
public void dumpStackTracesStarted() {
mDumpStackTracesStartUptime = getUptimeMillis();
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
"dumpStackTraces()");
}
- /** Records the end of ActivityManagerService#dumpStackTraces. */
+ /** Records the end of StackTracesDumpHelper#dumpStackTraces. */
public void dumpStackTracesEnded() {
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -328,7 +328,7 @@
anrSkipped("appNotResponding");
}
- /** Records a skipped ANR in ActivityManagerService#dumpStackTraces. */
+ /** Records a skipped ANR in StackTracesDumpHelper#dumpStackTraces. */
public void anrSkippedDumpStackTraces() {
anrSkipped("dumpStackTraces");
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 8f04cda..c1dbc87 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -111,6 +111,7 @@
void clickTile(in ComponentName tile);
@UnsupportedAppUsage
void handleSystemKey(in int key);
+ int getLastSystemKey();
/**
* Methods to show toast messages for screen pinning
diff --git a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
index a0f7905..f08e860 100644
--- a/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/CascadingMenuPopup.java
@@ -5,12 +5,14 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.text.TextFlags;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -46,8 +48,6 @@
*/
final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
PopupWindow.OnDismissListener {
- private static final int ITEM_LAYOUT = com.android.internal.R.layout.cascading_menu_item_layout;
-
@Retention(RetentionPolicy.SOURCE)
@IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
public @interface HorizPosition {}
@@ -190,6 +190,7 @@
private Callback mPresenterCallback;
private ViewTreeObserver mTreeObserver;
private PopupWindow.OnDismissListener mOnDismissListener;
+ private final int mItemLayout;
/** Whether popup menus should disable exit animations when closing. */
private boolean mShouldCloseImmediately;
@@ -215,6 +216,12 @@
res.getDimensionPixelSize(com.android.internal.R.dimen.config_prefDialogWidth));
mSubMenuHoverHandler = new Handler();
+
+ mItemLayout = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0
+ ? com.android.internal.R.layout.cascading_menu_item_layout_material
+ : com.android.internal.R.layout.cascading_menu_item_layout;
}
@Override
@@ -348,7 +355,7 @@
*/
private void showMenu(@NonNull MenuBuilder menu) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
- final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, ITEM_LAYOUT);
+ final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly, mItemLayout);
// Apply "force show icon" setting. There are 3 cases:
// (1) This is the top level menu and icon spacing is forced. Add spacing.
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 0d2b29b..cb1abf1 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -16,12 +16,12 @@
package com.android.internal.view.menu;
-import com.android.internal.R;
-
+import android.app.AppGlobals;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.TextFlags;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -61,6 +61,8 @@
private int mMenuType;
+ private boolean mUseNewContextMenu;
+
private LayoutInflater mInflater;
private boolean mForceShowIcon;
@@ -87,6 +89,10 @@
a.recycle();
b.recycle();
+
+ mUseNewContextMenu = AppGlobals.getIntCoreSetting(
+ TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU,
+ TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT ? 1 : 0) != 0;
}
public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
@@ -283,7 +289,9 @@
private void insertIconView() {
LayoutInflater inflater = getInflater();
- mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
+ mIconView = (ImageView) inflater.inflate(
+ mUseNewContextMenu ? com.android.internal.R.layout.list_menu_item_fixed_size_icon :
+ com.android.internal.R.layout.list_menu_item_icon,
this, false);
addContentView(mIconView, 0);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 12106c7..2f0ef14 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7401,6 +7401,8 @@
<p>Protection level: normal
-->
<permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"
+ android:label="@string/permlab_updatePackagesWithoutUserAction"
+ android:description="@string/permdesc_updatePackagesWithoutUserAction"
android:protectionLevel="normal" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION"/>
diff --git a/core/res/res/layout/cascading_menu_item_layout_material.xml b/core/res/res/layout/cascading_menu_item_layout_material.xml
new file mode 100644
index 0000000..168ed78
--- /dev/null
+++ b/core/res/res/layout/cascading_menu_item_layout_material.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Keep in sync with popup_menu_item_layout.xml (which only differs in the title and shortcut
+ position). -->
+<com.android.internal.view.menu.ListMenuItemView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minWidth="112dip"
+ android:maxWidth="280dip"
+ android:orientation="vertical" >
+
+ <ImageView
+ android:id="@+id/group_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dip"
+ android:layout_marginTop="8dip"
+ android:layout_marginBottom="8dip"
+ android:background="@drawable/list_divider_material" />
+
+ <LinearLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="48dip"
+ android:layout_marginStart="12dip"
+ android:layout_marginEnd="12dip"
+ android:duplicateParentState="true" >
+
+ <!-- Icon will be inserted here. -->
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="6dip"
+ android:textAppearance="?attr/textAppearanceLargePopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:textAlignment="viewStart" />
+
+ <Space
+ android:layout_width="0dip"
+ android:layout_height="1dip"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:id="@+id/shortcut"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="16dip"
+ android:textAppearance="?attr/textAppearanceSmallPopupMenu"
+ android:singleLine="true"
+ android:duplicateParentState="true"
+ android:textAlignment="viewEnd" />
+
+ <ImageView
+ android:id="@+id/submenuarrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
+ android:scaleType="center"
+ android:visibility="gone" />
+
+ <!-- Checkbox, and/or radio button will be inserted here. -->
+
+ </LinearLayout>
+
+</com.android.internal.view.menu.ListMenuItemView>
diff --git a/core/res/res/layout/list_menu_item_fixed_size_icon.xml b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
new file mode 100644
index 0000000..7797682
--- /dev/null
+++ b/core/res/res/layout/list_menu_item_fixed_size_icon.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="0dip"
+ android:layout_marginEnd="6dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:scaleType="centerInside"
+ android:duplicateParentState="true" />
+
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6afdae5..3ee8af2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2191,6 +2191,11 @@
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
<string name="permdesc_highSamplingRateSensors">Allows the app to sample sensor data at a rate greater than 200 Hz</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR_LIMIT=NONE] -->
+ <string name="permlab_updatePackagesWithoutUserAction">update app without user action</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this.[CHAR_LIMIT=NONE] -->
+ <string name="permdesc_updatePackagesWithoutUserAction">Allows the holder to update the app it previously installed without user action</string>
+
<!-- Policy administration -->
<!-- Title of policy access to limiting the user's password choices -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8fb7c9d..5c734df 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1476,6 +1476,7 @@
<java-symbol type="layout" name="action_mode_close_item" />
<java-symbol type="layout" name="alert_dialog" />
<java-symbol type="layout" name="cascading_menu_item_layout" />
+ <java-symbol type="layout" name="cascading_menu_item_layout_material" />
<java-symbol type="layout" name="choose_account" />
<java-symbol type="layout" name="choose_account_row" />
<java-symbol type="layout" name="choose_account_type" />
@@ -1525,6 +1526,7 @@
<java-symbol type="layout" name="list_content_simple" />
<java-symbol type="layout" name="list_menu_item_checkbox" />
<java-symbol type="layout" name="list_menu_item_icon" />
+ <java-symbol type="layout" name="list_menu_item_fixed_size_icon" />
<java-symbol type="layout" name="list_menu_item_layout" />
<java-symbol type="layout" name="list_menu_item_radio" />
<java-symbol type="layout" name="locale_picker_item" />
diff --git a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java b/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
deleted file mode 100644
index f96d138..0000000
--- a/core/tests/coretests/src/android/companion/virtual/camera/VirtualCameraOutputTest.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.companion.virtual.camera;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.fail;
-
-import android.graphics.PixelFormat;
-import android.hardware.camera2.params.InputConfiguration;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.ByteArrayInputStream;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class VirtualCameraOutputTest {
-
- private static final String TAG = "VirtualCameraOutputTest";
-
- private ExecutorService mExecutor;
-
- private InputConfiguration mConfiguration;
-
- @Before
- public void setUp() {
- mExecutor = Executors.newSingleThreadExecutor();
- mConfiguration = new InputConfiguration(64, 64, PixelFormat.RGB_888);
- }
-
- @After
- public void cleanUp() {
- mExecutor.shutdownNow();
- }
-
- @Test
- public void createStreamDescriptor_successfulDataStream() {
- byte[] cameraData = new byte[]{1, 2, 3, 4, 5};
- VirtualCameraInput input = createCameraInput(cameraData);
- VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
- ParcelFileDescriptor descriptor = output.getStreamDescriptor(mConfiguration);
-
- try (FileInputStream fis = new FileInputStream(descriptor.getFileDescriptor())) {
- byte[] receivedData = fis.readNBytes(cameraData.length);
-
- output.closeStream();
- assertThat(receivedData).isEqualTo(cameraData);
- } catch (IOException exception) {
- fail("Unable to read bytes from FileInputStream. Message: " + exception.getMessage());
- }
- }
-
- @Test
- public void createStreamDescriptor_multipleCallsSameStream() {
- VirtualCameraInput input = createCameraInput(new byte[]{0});
- VirtualCameraOutput output = new VirtualCameraOutput(input, mExecutor);
-
- ParcelFileDescriptor firstDescriptor = output.getStreamDescriptor(mConfiguration);
- ParcelFileDescriptor secondDescriptor = output.getStreamDescriptor(mConfiguration);
-
- assertThat(firstDescriptor).isSameInstanceAs(secondDescriptor);
- }
-
- @Test
- public void createStreamDescriptor_differentStreams() {
- VirtualCameraInput input = createCameraInput(new byte[]{0});
- VirtualCameraOutput callback = new VirtualCameraOutput(input, mExecutor);
-
- InputConfiguration differentConfig = new InputConfiguration(mConfiguration.getWidth() + 1,
- mConfiguration.getHeight() + 1, mConfiguration.getFormat());
-
- ParcelFileDescriptor firstDescriptor = callback.getStreamDescriptor(mConfiguration);
- ParcelFileDescriptor secondDescriptor = callback.getStreamDescriptor(differentConfig);
-
- assertThat(firstDescriptor).isNotSameInstanceAs(secondDescriptor);
- }
-
- private VirtualCameraInput createCameraInput(byte[] data) {
- return new VirtualCameraInput() {
- private ByteArrayInputStream mInputStream = null;
-
- @Override
- @NonNull
- public InputStream openStream(@NonNull InputConfiguration inputConfiguration) {
- closeStream();
- mInputStream = new ByteArrayInputStream(data);
- return mInputStream;
- }
-
- @Override
- public void closeStream() {
- if (mInputStream == null) {
- return;
- }
- try {
- mInputStream.close();
- } catch (IOException e) {
- Log.e(TAG, "Unable to close image stream.", e);
- }
- mInputStream = null;
- }
- };
- }
-}
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index 6f0c3d3..c7e0261 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -49,6 +49,7 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -126,11 +127,11 @@
null, List.of(Slice.HINT_TITLE)).build();
mRegisterRequest = new RegisterCredentialDescriptionRequest(
new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
- "{ \"foo\": \"bar\" }",
+ new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
mUnregisterRequest = new UnregisterCredentialDescriptionRequest(
new CredentialDescription(Credential.TYPE_PASSWORD_CREDENTIAL,
- "{ \"foo\": \"bar\" }",
+ new HashSet<>(List.of("{ \"foo\": \"bar\" }")),
List.of(new CredentialEntry(Credential.TYPE_PASSWORD_CREDENTIAL, slice))));
final Context context = InstrumentationRegistry.getInstrumentation().getContext();
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 60898ef..51f99ec 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -914,7 +914,6 @@
case "image/gif":
case "image/heif":
case "image/heic":
- case "image/avif":
case "image/bmp":
case "image/x-ico":
case "image/vnd.wap.wbmp":
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 404429a..89f4890 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,7 +40,9 @@
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
+import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
import android.app.Activity;
@@ -1037,9 +1039,15 @@
final TaskFragmentContainer primaryContainer = getContainerWithActivity(
primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
- final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+ final TaskContainer.TaskProperties taskProperties = mPresenter
+ .getTaskProperties(primaryActivity);
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+ getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
- && canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
+ && canReuseContainer(splitRule, splitContainer.getSplitRule(),
+ getTaskWindowMetrics(taskProperties.getConfiguration()),
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
// Can launch in the existing secondary container if the rules share the same
// presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1058,7 +1066,8 @@
}
}
// Create new split pair.
- mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule);
+ mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule,
+ calculatedSplitAttributes);
return true;
}
@@ -1283,9 +1292,16 @@
}
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
- final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
+ final TaskContainer.TaskProperties taskProperties = mPresenter
+ .getTaskProperties(primaryActivity);
+ final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
+ taskProperties.getConfiguration());
+ final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
+ taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
+ getActivityIntentMinDimensionsPair(primaryActivity, intent));
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
- && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
+ && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics,
+ calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())
// TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1296,7 +1312,7 @@
}
// Create a new TaskFragment to split with the primary activity for the new activity.
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
- splitRule);
+ splitRule, calculatedSplitAttributes);
}
/**
@@ -2273,21 +2289,29 @@
}
/**
- * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
- * there is any.
+ * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
+ * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
+ * {@link SplitContainer} if there is any.
*/
private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
- @NonNull WindowMetrics parentWindowMetrics) {
+ @NonNull WindowMetrics parentWindowMetrics,
+ @NonNull SplitAttributes calculatedSplitAttributes,
+ @NonNull SplitAttributes containerSplitAttributes) {
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false;
}
- return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
- parentWindowMetrics);
+ return areRulesSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
+ parentWindowMetrics)
+ // Besides rules, we should also check whether the SplitContainer's splitAttributes
+ // matches the current splitAttributes or not. The splitAttributes may change
+ // if the app chooses different SplitAttributes calculator function before a new
+ // activity is started even they match the same splitRule.
+ && calculatedSplitAttributes.equals(containerSplitAttributes);
}
/** Whether the two rules have the same presentation. */
@VisibleForTesting
- static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
+ static boolean areRulesSamePresentation(@NonNull SplitPairRule rule1,
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
if (rule1.getTag() != null || rule2.getTag() != null) {
// Tag must be unique if it is set. We don't want to reuse the container if the rules
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 0408511..20b6ae2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -174,12 +174,9 @@
@NonNull
TaskFragmentContainer createNewSplitWithEmptySideContainer(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
- @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
+ @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule,
+ @NonNull SplitAttributes splitAttributes) {
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
- final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
- primaryActivity, secondaryIntent);
- final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -217,15 +214,12 @@
* same container as the primary activity, a new container will be
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
+ * @param splitAttributes The {@link SplitAttributes} to apply
*/
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
- @NonNull SplitPairRule rule) {
+ @NonNull SplitPairRule rule, @NonNull SplitAttributes splitAttributes) {
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
- final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
- secondaryActivity);
- final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
- rule.getDefaultSplitAttributes(), minDimensionsPair);
final Rect primaryRelBounds = getRelBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
@@ -654,7 +648,7 @@
}
@NonNull
- private static Pair<Size, Size> getActivitiesMinDimensionsPair(
+ static Pair<Size, Size> getActivitiesMinDimensionsPair(
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {
return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity));
}
@@ -1027,7 +1021,7 @@
}
@NonNull
- private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
+ static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
// TODO(b/190433398): Supply correct insets.
final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index cbdc262..ff08782 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -565,7 +565,6 @@
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
assertTrue(container.areLastRequestedBoundsEqual(null));
- assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
}
@Test
@@ -1008,9 +1007,8 @@
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
- assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
- mSplitController.getContainerWithActivity(mActivity));
- verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any());
+ assertTrue(mSplitController.getContainerWithActivity(mActivity)
+ .areLastRequestedBoundsEqual(new Rect()));
}
@Test
@@ -1215,7 +1213,7 @@
.build();
assertTrue("Rules must have same presentation if tags are null and has same properties.",
- SplitController.haveSamePresentation(splitRule1, splitRule2,
+ SplitController.areRulesSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
splitRule2 = createSplitPairRuleBuilder(
@@ -1230,7 +1228,7 @@
assertFalse("Rules must have different presentations if tags are not equal regardless"
+ "of other properties",
- SplitController.haveSamePresentation(splitRule1, splitRule2,
+ SplitController.areRulesSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 8330156..8dd1384 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -678,7 +678,8 @@
.setShouldClearTop(false)
.build();
- mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule);
+ mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule,
+ SPLIT_ATTRIBUTES);
assertEquals(primaryTf, mController.getContainerWithActivity(mActivity));
final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity(
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index c7c9424..54978bd 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -46,6 +46,8 @@
"src/com/android/wm/shell/common/split/SplitScreenConstants.java",
"src/com/android/wm/shell/sysui/ShellSharedConstants.java",
"src/com/android/wm/shell/common/TransactionPool.java",
+ "src/com/android/wm/shell/common/bubbles/*.java",
+ "src/com/android/wm/shell/common/TriangleShape.java",
"src/com/android/wm/shell/animation/Interpolators.java",
"src/com/android/wm/shell/pip/PipContentOverlay.java",
"src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index ae33b94..4eaedd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -25,7 +25,10 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.FloatProperty;
@@ -34,6 +37,7 @@
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.view.WindowManager.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
@@ -43,6 +47,7 @@
import android.window.BackProgressAnimator;
import android.window.IOnBackInvokedCallback;
+import com.android.internal.R;
import com.android.internal.dynamicanimation.animation.SpringAnimation;
import com.android.internal.dynamicanimation.animation.SpringForce;
import com.android.internal.policy.ScreenDecorationsUtils;
@@ -78,6 +83,7 @@
final CustomAnimationLoader mCustomAnimationLoader;
private Animation mEnterAnimation;
private Animation mCloseAnimation;
+ private int mNextBackgroundColor;
final Transformation mTransformation = new Transformation();
private final Choreographer mChoreographer;
@@ -144,8 +150,9 @@
// Draw background with task background color.
if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
- mBackground.ensureBackground(
- mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ mBackground.ensureBackground(mNextBackgroundColor == Color.TRANSPARENT
+ ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor()
+ : mNextBackgroundColor, mTransaction);
}
}
@@ -191,6 +198,7 @@
mTransaction.apply();
mTransformation.clear();
mLatestProgress = 0;
+ mNextBackgroundColor = Color.TRANSPARENT;
if (mFinishCallback != null) {
try {
mFinishCallback.onAnimationFinished();
@@ -252,11 +260,11 @@
* Load customize animation before animation start.
*/
boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
- mCloseAnimation = mCustomAnimationLoader.load(
- animationInfo, false /* enterAnimation */);
- if (mCloseAnimation != null) {
- mEnterAnimation = mCustomAnimationLoader.load(
- animationInfo, true /* enterAnimation */);
+ final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
+ if (result != null) {
+ mCloseAnimation = result.mCloseAnimation;
+ mEnterAnimation = result.mEnterAnimation;
+ mNextBackgroundColor = result.mBackgroundColor;
return true;
}
return false;
@@ -318,35 +326,79 @@
}
}
+
+ static final class AnimationLoadResult {
+ Animation mCloseAnimation;
+ Animation mEnterAnimation;
+ int mBackgroundColor;
+ }
+
/**
* Helper class to load custom animation.
*/
static class CustomAnimationLoader {
- private final TransitionAnimation mTransitionAnimation;
+ final TransitionAnimation mTransitionAnimation;
CustomAnimationLoader(Context context) {
mTransitionAnimation = new TransitionAnimation(
context, false /* debug */, "CustomizeBackAnimation");
}
- Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
+ /**
+ * Load both enter and exit animation for the close activity transition.
+ * Note that the result is only valid if the exit animation has set and loaded success.
+ * If the entering animation has not set(i.e. 0), here will load the default entering
+ * animation for it.
+ *
+ * @param animationInfo The information of customize animation, which can be set from
+ * {@link Activity#overrideActivityTransition} and/or
+ * {@link LayoutParams#windowAnimations}
+ */
+ AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+ if (animationInfo.getPackageName().isEmpty()) {
+ return null;
+ }
+ final Animation close = loadAnimation(animationInfo, false);
+ if (close == null) {
+ return null;
+ }
+ final Animation open = loadAnimation(animationInfo, true);
+ AnimationLoadResult result = new AnimationLoadResult();
+ result.mCloseAnimation = close;
+ result.mEnterAnimation = open;
+ result.mBackgroundColor = animationInfo.getCustomBackground();
+ return result;
+ }
+
+ /**
+ * Load enter or exit animation from CustomAnimationInfo
+ * @param animationInfo The information for customize animation.
+ * @param enterAnimation true when load for enter animation, false for exit animation.
+ * @return Loaded animation.
+ */
+ @Nullable
+ Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo,
boolean enterAnimation) {
- final String packageName = animationInfo.getPackageName();
- if (packageName.isEmpty()) {
- return null;
+ Animation a = null;
+ // Activity#overrideActivityTransition has higher priority than windowAnimations
+ // Try to get animation from Activity#overrideActivityTransition
+ if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0)
+ || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) {
+ a = mTransitionAnimation.loadAppTransitionAnimation(
+ animationInfo.getPackageName(),
+ enterAnimation ? animationInfo.getCustomEnterAnim()
+ : animationInfo.getCustomExitAnim());
+ } else if (animationInfo.getWindowAnimations() != 0) {
+ // try to get animation from LayoutParams#windowAnimations
+ a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(),
+ animationInfo.getWindowAnimations(), enterAnimation
+ ? R.styleable.WindowAnimation_activityCloseEnterAnimation
+ : R.styleable.WindowAnimation_activityCloseExitAnimation,
+ false /* translucent */);
}
- final int windowAnimations = animationInfo.getWindowAnimations();
- if (windowAnimations == 0) {
- return null;
- }
- final int attrs = enterAnimation
- ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
- : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
- Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
- attrs, false /* translucent */);
// Only allow to load default animation for opening target.
if (a == null && enterAnimation) {
- a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
+ a = loadDefaultOpenAnimation();
}
if (a != null) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
@@ -355,5 +407,11 @@
}
return a;
}
+
+ private Animation loadDefaultOpenAnimation() {
+ return mTransitionAnimation.loadDefaultAnimationAttr(
+ R.styleable.WindowAnimation_activityCloseEnterAnimation,
+ false /* translucent */);
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 85a353f..4805ed3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -47,6 +47,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.common.bubbles.BubbleInfo;
import java.io.PrintWriter;
import java.util.List;
@@ -244,6 +245,16 @@
setEntry(entry);
}
+ /** Converts this bubble into a {@link BubbleInfo} object to be shared with external callers. */
+ public BubbleInfo asBubbleBarBubble() {
+ return new BubbleInfo(getKey(),
+ getFlags(),
+ getShortcutInfo().getId(),
+ getIcon(),
+ getUser().getIdentifier(),
+ getPackageName());
+ }
+
@Override
public String getKey() {
return mKey;
@@ -545,8 +556,13 @@
}
}
+ /**
+ * @return the icon set on BubbleMetadata, if it exists. This is only non-null for bubbles
+ * created via a PendingIntent. This is null for bubbles created by a shortcut, as we use the
+ * icon from the shortcut.
+ */
@Nullable
- Icon getIcon() {
+ public Icon getIcon() {
return mIcon;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d2889e7..4b4b1af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -38,7 +38,9 @@
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
+import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -59,6 +61,7 @@
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -88,13 +91,17 @@
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.SingleInstanceRemoteListener;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
@@ -123,7 +130,8 @@
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController implements ConfigurationChangeListener {
+public class BubbleController implements ConfigurationChangeListener,
+ RemoteCallable<BubbleController> {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -248,6 +256,8 @@
private Optional<OneHandedController> mOneHandedOptional;
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
+ /** Used to send bubble events to launcher. */
+ private Bubbles.BubbleStateListener mBubbleStateListener;
public BubbleController(Context context,
ShellInit shellInit,
@@ -458,9 +468,15 @@
mCurrentProfiles = userProfiles;
mShellController.addConfigurationChangeListener(this);
+ mShellController.addExternalInterface(KEY_EXTRA_SHELL_BUBBLES,
+ this::createExternalInterface, this);
mShellCommandHandler.addDumpCallback(this::dump, this);
}
+ private ExternalInterfaceBinder createExternalInterface() {
+ return new BubbleController.IBubblesImpl(this);
+ }
+
@VisibleForTesting
public Bubbles asBubbles() {
return mImpl;
@@ -475,6 +491,48 @@
return mMainExecutor;
}
+ @Override
+ public Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ShellExecutor getRemoteCallExecutor() {
+ return mMainExecutor;
+ }
+
+ /**
+ * Sets a listener to be notified of bubble updates. This is used by launcher so that
+ * it may render bubbles in itself. Only one listener is supported.
+ */
+ public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
+ if (isShowingAsBubbleBar()) {
+ // Only set the listener if bubble bar is showing.
+ mBubbleStateListener = listener;
+ sendInitialListenerUpdate();
+ } else {
+ mBubbleStateListener = null;
+ }
+ }
+
+ /**
+ * Unregisters the {@link Bubbles.BubbleStateListener}.
+ */
+ public void unregisterBubbleStateListener() {
+ mBubbleStateListener = null;
+ }
+
+ /**
+ * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble
+ * state to it.
+ */
+ private void sendInitialListenerUpdate() {
+ if (mBubbleStateListener != null) {
+ BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar();
+ mBubbleStateListener.onBubbleStateChange(update);
+ }
+ }
+
/**
* Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
*/
@@ -1722,6 +1780,73 @@
}
}
+ /**
+ * The interface for calls from outside the host process.
+ */
+ @BinderThread
+ private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder {
+ private BubbleController mController;
+ private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener;
+ private final Bubbles.BubbleStateListener mBubbleListener =
+ new Bubbles.BubbleStateListener() {
+
+ @Override
+ public void onBubbleStateChange(BubbleBarUpdate update) {
+ Bundle b = new Bundle();
+ b.setClassLoader(BubbleBarUpdate.class.getClassLoader());
+ b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update);
+ mListener.call(l -> l.onBubbleStateChange(b));
+ }
+ };
+
+ IBubblesImpl(BubbleController controller) {
+ mController = controller;
+ mListener = new SingleInstanceRemoteListener<>(mController,
+ c -> c.registerBubbleStateListener(mBubbleListener),
+ c -> c.unregisterBubbleStateListener());
+ }
+
+ /**
+ * Invalidates this instance, preventing future calls from updating the controller.
+ */
+ @Override
+ public void invalidate() {
+ mController = null;
+ }
+
+ @Override
+ public void registerBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> {
+ mListener.register(listener);
+ });
+ }
+
+ @Override
+ public void unregisterBubbleListener(IBubblesListener listener) {
+ mMainExecutor.execute(() -> mListener.unregister());
+ }
+
+ @Override
+ public void showBubble(String key, boolean onLauncherHome) {
+ // TODO
+ }
+
+ @Override
+ public void removeBubble(String key, int reason) {
+ // TODO
+ }
+
+ @Override
+ public void collapseBubbles() {
+ // TODO
+ }
+
+ @Override
+ public void onTaskbarStateChanged(int newState) {
+ // TODO (b/269670598)
+ }
+ }
+
private class BubblesImpl implements Bubbles {
// Up-to-date cached state of bubbles data for SysUI to query from the calling thread
@VisibleForTesting
@@ -1835,6 +1960,17 @@
private CachedState mCachedState = new CachedState();
+ private IBubblesImpl mIBubbles;
+
+ @Override
+ public IBubbles createExternalInterface() {
+ if (mIBubbles != null) {
+ mIBubbles.invalidate();
+ }
+ mIBubbles = new IBubblesImpl(BubbleController.this);
+ return mIBubbles;
+ }
+
@Override
public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 3fd0967..a26c0c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -40,6 +40,8 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.bubbles.Bubbles.DismissReason;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.common.bubbles.RemovedBubble;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -113,6 +115,61 @@
void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
removedBubbles.add(new Pair<>(bubbleToRemove, reason));
}
+
+ /**
+ * Converts the update to a {@link BubbleBarUpdate} which contains updates relevant
+ * to the bubble bar. Only used when {@link BubbleController#isShowingAsBubbleBar()} is
+ * true.
+ */
+ BubbleBarUpdate toBubbleBarUpdate() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+
+ bubbleBarUpdate.expandedChanged = expandedChanged;
+ bubbleBarUpdate.expanded = expanded;
+ if (selectionChanged) {
+ bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
+ ? selectedBubble.getKey()
+ : null;
+ }
+ bubbleBarUpdate.addedBubble = addedBubble != null
+ ? addedBubble.asBubbleBarBubble()
+ : null;
+ // TODO(b/269670235): We need to handle updates better, I think for the bubble bar only
+ // certain updates need to be sent instead of any updatedBubble.
+ bubbleBarUpdate.updatedBubble = updatedBubble != null
+ ? updatedBubble.asBubbleBarBubble()
+ : null;
+ bubbleBarUpdate.suppressedBubbleKey = suppressedBubble != null
+ ? suppressedBubble.getKey()
+ : null;
+ bubbleBarUpdate.unsupressedBubbleKey = unsuppressedBubble != null
+ ? unsuppressedBubble.getKey()
+ : null;
+ for (int i = 0; i < removedBubbles.size(); i++) {
+ Pair<Bubble, Integer> pair = removedBubbles.get(i);
+ bubbleBarUpdate.removedBubbles.add(
+ new RemovedBubble(pair.first.getKey(), pair.second));
+ }
+ if (orderChanged) {
+ // Include the new order
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.bubbleKeysInOrder.add(bubbles.get(i).getKey());
+ }
+ }
+ return bubbleBarUpdate;
+ }
+
+ /**
+ * Gets the current state of active bubbles and populates the update with that. Only
+ * used when {@link BubbleController#isShowingAsBubbleBar()} is true.
+ */
+ BubbleBarUpdate getInitialState() {
+ BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+ for (int i = 0; i < bubbles.size(); i++) {
+ bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
+ }
+ return bubbleBarUpdate;
+ }
}
/**
@@ -190,6 +247,13 @@
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
}
+ /**
+ * Returns a bubble bar update populated with the current list of active bubbles.
+ */
+ public BubbleBarUpdate getInitialStateForBubbleBar() {
+ return mStateChange.getInitialState();
+ }
+
public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) {
mBubbleMetadataFlagListener = listener;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
new file mode 100644
index 0000000..2a31629
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
+
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.TaskView;
+import com.android.wm.shell.TaskViewTaskController;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}.
+ */
+public class BubbleTaskViewHelper {
+
+ private static final String TAG = BubbleTaskViewHelper.class.getSimpleName();
+
+ /**
+ * Listener for users of {@link BubbleTaskViewHelper} to use to be notified of events
+ * on the task.
+ */
+ public interface Listener {
+
+ /** Called when the task is first created. */
+ void onTaskCreated();
+
+ /** Called when the visibility of the task changes. */
+ void onContentVisibilityChanged(boolean visible);
+
+ /** Called when back is pressed on the task root. */
+ void onBackPressed();
+ }
+
+ private final Context mContext;
+ private final BubbleController mController;
+ private final @ShellMainThread ShellExecutor mMainExecutor;
+ private final BubbleTaskViewHelper.Listener mListener;
+ private final View mParentView;
+
+ @Nullable
+ private Bubble mBubble;
+ @Nullable
+ private PendingIntent mPendingIntent;
+ private TaskViewTaskController mTaskViewTaskController;
+ @Nullable
+ private TaskView mTaskView;
+ private int mTaskId = INVALID_TASK_ID;
+
+ private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
+ private boolean mInitialized = false;
+ private boolean mDestroyed = false;
+
+ @Override
+ public void onInitialized() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: destroyed=" + mDestroyed
+ + " initialized=" + mInitialized
+ + " bubble=" + getBubbleKey());
+ }
+
+ if (mDestroyed || mInitialized) {
+ return;
+ }
+
+ // Custom options so there is no activity transition animation
+ ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext,
+ 0 /* enterResId */, 0 /* exitResId */);
+
+ Rect launchBounds = new Rect();
+ mTaskView.getBoundsOnScreen(launchBounds);
+
+ // TODO: I notice inconsistencies in lifecycle
+ // Post to keep the lifecycle normal
+ mParentView.post(() -> {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onInitialized: calling startActivity, bubble="
+ + getBubbleKey());
+ }
+ try {
+ options.setTaskAlwaysOnTop(true);
+ options.setLaunchedFromBubble(true);
+
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+
+ if (mBubble.isAppBubble()) {
+ PendingIntent pi = PendingIntent.getActivity(mContext, 0,
+ mBubble.getAppBubbleIntent(),
+ PendingIntent.FLAG_MUTABLE,
+ null);
+ mTaskView.startActivity(pi, fillInIntent, options, launchBounds);
+ } else if (mBubble.hasMetadataShortcutId()) {
+ options.setApplyActivityFlagsForBubbles(true);
+ mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
+ options, launchBounds);
+ } else {
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
+ mTaskView.startActivity(mPendingIntent, fillInIntent, options,
+ launchBounds);
+ }
+ } catch (RuntimeException e) {
+ // If there's a runtime exception here then there's something
+ // wrong with the intent, we can't really recover / try to populate
+ // the bubble again so we'll just remove it.
+ Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ + ", " + e.getMessage() + "; removing bubble");
+ mController.removeBubble(getBubbleKey(), Bubbles.DISMISS_INVALID_INTENT);
+ }
+ mInitialized = true;
+ });
+ }
+
+ @Override
+ public void onReleased() {
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName name) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskCreated: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
+ mTaskId = taskId;
+
+ // With the task org, the taskAppeared callback will only happen once the task has
+ // already drawn
+ mListener.onTaskCreated();
+ }
+
+ @Override
+ public void onTaskVisibilityChanged(int taskId, boolean visible) {
+ mListener.onContentVisibilityChanged(visible);
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
+ + " bubble=" + getBubbleKey());
+ }
+ if (mBubble != null) {
+ mController.removeBubble(mBubble.getKey(), Bubbles.DISMISS_TASK_FINISHED);
+ }
+ }
+
+ @Override
+ public void onBackPressedOnTaskRoot(int taskId) {
+ if (mTaskId == taskId && mController.isStackExpanded()) {
+ mListener.onBackPressed();
+ }
+ }
+ };
+
+ public BubbleTaskViewHelper(Context context,
+ BubbleController controller,
+ BubbleTaskViewHelper.Listener listener,
+ View parent) {
+ mContext = context;
+ mController = controller;
+ mMainExecutor = mController.getMainExecutor();
+ mListener = listener;
+ mParentView = parent;
+ mTaskViewTaskController = new TaskViewTaskController(mContext,
+ mController.getTaskOrganizer(),
+ mController.getTaskViewTransitions(), mController.getSyncTransactionQueue());
+ mTaskView = new TaskView(mContext, mTaskViewTaskController);
+ mTaskView.setListener(mMainExecutor, mTaskViewListener);
+ }
+
+ /**
+ * Sets the bubble or updates the bubble used to populate the view.
+ *
+ * @return true if the bubble is new, false if it was an update to the same bubble.
+ */
+ public boolean update(Bubble bubble) {
+ boolean isNew = mBubble == null || didBackingContentChange(bubble);
+ mBubble = bubble;
+ if (isNew) {
+ mPendingIntent = mBubble.getBubbleIntent();
+ return true;
+ }
+ return false;
+ }
+
+ /** Cleans up anything related to the task and {@code TaskView}. */
+ public void cleanUpTaskView() {
+ if (DEBUG_BUBBLE_EXPANDED_VIEW) {
+ Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
+ }
+ if (mTaskId != INVALID_TASK_ID) {
+ try {
+ ActivityTaskManager.getService().removeTask(mTaskId);
+ } catch (RemoteException e) {
+ Log.w(TAG, e.getMessage());
+ }
+ }
+ if (mTaskView != null) {
+ mTaskView.release();
+ mTaskView = null;
+ }
+ }
+
+ /** Returns the bubble key associated with this view. */
+ @Nullable
+ public String getBubbleKey() {
+ return mBubble != null ? mBubble.getKey() : null;
+ }
+
+ /** Returns the TaskView associated with this view. */
+ @Nullable
+ public TaskView getTaskView() {
+ return mTaskView;
+ }
+
+ /**
+ * Returns the task id associated with the task in this view. If the task doesn't exist then
+ * {@link ActivityTaskManager#INVALID_TASK_ID}.
+ */
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ /** Returns whether the bubble set on the helper is valid to populate the task view. */
+ public boolean isValidBubble() {
+ return mBubble != null && (mPendingIntent != null || mBubble.hasMetadataShortcutId());
+ }
+
+ // TODO (b/274980695): Is this still relevant?
+ /**
+ * Bubbles are backed by a pending intent or a shortcut, once the activity is
+ * started we never change it / restart it on notification updates -- unless the bubble's
+ * backing data switches.
+ *
+ * This indicates if the new bubble is backed by a different data source than what was
+ * previously shown here (e.g. previously a pending intent & now a shortcut).
+ *
+ * @param newBubble the bubble this view is being updated with.
+ * @return true if the backing content has changed.
+ */
+ private boolean didBackingContentChange(Bubble newBubble) {
+ boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
+ boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
+ return prevWasIntentBased != newIsIntentBased;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 876a720..259f692 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -39,6 +39,7 @@
import androidx.annotation.Nullable;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -81,6 +82,11 @@
int DISMISS_RELOAD_FROM_DISK = 15;
int DISMISS_USER_REMOVED = 16;
+ /** Returns a binder that can be passed to an external process to manipulate Bubbles. */
+ default IBubbles createExternalInterface() {
+ return null;
+ }
+
/**
* @return {@code true} if there is a bubble associated with the provided key and if its
* notification is hidden from the shade or there is a group summary associated with the
@@ -277,6 +283,17 @@
*/
void onUserRemoved(int removedUserId);
+ /**
+ * A listener to be notified of bubble state changes, used by launcher to render bubbles in
+ * its process.
+ */
+ interface BubbleStateListener {
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(BubbleBarUpdate update);
+ }
+
/** Listener to find out about stack expansion / collapse events. */
interface BubbleExpandListener {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
new file mode 100644
index 0000000..862e818
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.content.Intent;
+import com.android.wm.shell.bubbles.IBubblesListener;
+
+/**
+ * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
+ * showing in the bubble bar.
+ */
+interface IBubbles {
+
+ oneway void registerBubbleListener(in IBubblesListener listener) = 1;
+
+ oneway void unregisterBubbleListener(in IBubblesListener listener) = 2;
+
+ oneway void showBubble(in String key, in boolean onLauncherHome) = 3;
+
+ oneway void removeBubble(in String key, in int reason) = 4;
+
+ oneway void collapseBubbles() = 5;
+
+ oneway void onTaskbarStateChanged(in int newState) = 6;
+
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
new file mode 100644
index 0000000..e48f8d5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubblesListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+import android.os.Bundle;
+
+/**
+ * Listener interface that Launcher attaches to SystemUI to get bubbles callbacks.
+ */
+oneway interface IBubblesListener {
+
+ /**
+ * Called when the bubbles state changes.
+ */
+ void onBubbleStateChange(in Bundle update);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
new file mode 100644
index 0000000..8142347
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents an update to bubbles state. This is passed through
+ * {@link com.android.wm.shell.bubbles.IBubblesListener} to launcher so that taskbar may render
+ * bubbles. This should be kept this as minimal as possible in terms of data.
+ */
+public class BubbleBarUpdate implements Parcelable {
+
+ public static final String BUNDLE_KEY = "update";
+
+ public boolean expandedChanged;
+ public boolean expanded;
+ @Nullable
+ public String selectedBubbleKey;
+ @Nullable
+ public BubbleInfo addedBubble;
+ @Nullable
+ public BubbleInfo updatedBubble;
+ @Nullable
+ public String suppressedBubbleKey;
+ @Nullable
+ public String unsupressedBubbleKey;
+
+ // This is only populated if bubbles have been removed.
+ public List<RemovedBubble> removedBubbles = new ArrayList<>();
+
+ // This is only populated if the order of the bubbles has changed.
+ public List<String> bubbleKeysInOrder = new ArrayList<>();
+
+ // This is only populated the first time a listener is connected so it gets the current state.
+ public List<BubbleInfo> currentBubbleList = new ArrayList<>();
+
+ public BubbleBarUpdate() {
+ }
+
+ public BubbleBarUpdate(Parcel parcel) {
+ expandedChanged = parcel.readBoolean();
+ expanded = parcel.readBoolean();
+ selectedBubbleKey = parcel.readString();
+ addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ updatedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
+ BubbleInfo.class);
+ suppressedBubbleKey = parcel.readString();
+ unsupressedBubbleKey = parcel.readString();
+ removedBubbles = parcel.readParcelableList(new ArrayList<>(),
+ RemovedBubble.class.getClassLoader());
+ parcel.readStringList(bubbleKeysInOrder);
+ currentBubbleList = parcel.readParcelableList(new ArrayList<>(),
+ BubbleInfo.class.getClassLoader());
+ }
+
+ /**
+ * Returns whether anything has changed in this update.
+ */
+ public boolean anythingChanged() {
+ return expandedChanged
+ || selectedBubbleKey != null
+ || addedBubble != null
+ || updatedBubble != null
+ || !removedBubbles.isEmpty()
+ || !bubbleKeysInOrder.isEmpty()
+ || suppressedBubbleKey != null
+ || unsupressedBubbleKey != null
+ || !currentBubbleList.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
+ + " expanded=" + expanded
+ + " selectedBubbleKey=" + selectedBubbleKey
+ + " addedBubble=" + addedBubble
+ + " updatedBubble=" + updatedBubble
+ + " suppressedBubbleKey=" + suppressedBubbleKey
+ + " unsuppressedBubbleKey=" + unsupressedBubbleKey
+ + " removedBubbles=" + removedBubbles
+ + " bubbles=" + bubbleKeysInOrder
+ + " currentBubbleList=" + currentBubbleList
+ + " }";
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeBoolean(expandedChanged);
+ parcel.writeBoolean(expanded);
+ parcel.writeString(selectedBubbleKey);
+ parcel.writeParcelable(addedBubble, flags);
+ parcel.writeParcelable(updatedBubble, flags);
+ parcel.writeString(suppressedBubbleKey);
+ parcel.writeString(unsupressedBubbleKey);
+ parcel.writeParcelableList(removedBubbles, flags);
+ parcel.writeStringList(bubbleKeysInOrder);
+ parcel.writeParcelableList(currentBubbleList, flags);
+ }
+
+ @NonNull
+ public static final Creator<BubbleBarUpdate> CREATOR =
+ new Creator<BubbleBarUpdate>() {
+ public BubbleBarUpdate createFromParcel(Parcel source) {
+ return new BubbleBarUpdate(source);
+ }
+ public BubbleBarUpdate[] newArray(int size) {
+ return new BubbleBarUpdate[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
new file mode 100644
index 0000000..b0dea72
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleInfo.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.graphics.drawable.Icon;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Contains information necessary to present a bubble.
+ */
+public class BubbleInfo implements Parcelable {
+
+ // TODO(b/269672147): needs a title string for a11y & that comes from notification
+ // TODO(b/269671451): needs whether the bubble is an 'important person' or not
+
+ private String mKey; // Same key as the Notification
+ private int mFlags; // Flags from BubbleMetadata
+ private String mShortcutId;
+ private int mUserId;
+ private String mPackageName;
+ /**
+ * All notification bubbles require a shortcut to be set on the notification, however, the
+ * app could still specify an Icon and PendingIntent to use for the bubble. In that case
+ * this icon will be populated. If the bubble is entirely shortcut based, this will be null.
+ */
+ @Nullable
+ private Icon mIcon;
+
+ public BubbleInfo(String key, int flags, String shortcutId, @Nullable Icon icon,
+ int userId, String packageName) {
+ mKey = key;
+ mFlags = flags;
+ mShortcutId = shortcutId;
+ mIcon = icon;
+ mUserId = userId;
+ mPackageName = packageName;
+ }
+
+ public BubbleInfo(Parcel source) {
+ mKey = source.readString();
+ mFlags = source.readInt();
+ mShortcutId = source.readString();
+ mIcon = source.readTypedObject(Icon.CREATOR);
+ mUserId = source.readInt();
+ mPackageName = source.readString();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public String getShortcutId() {
+ return mShortcutId;
+ }
+
+ public Icon getIcon() {
+ return mIcon;
+ }
+
+ public int getFlags() {
+ return mFlags;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Whether this bubble is currently being hidden from the stack.
+ */
+ public boolean isBubbleSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether this bubble is able to be suppressed (i.e. has the developer opted into the API
+ * to
+ * hide the bubble when in the same content).
+ */
+ public boolean isBubbleSuppressable() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE) != 0;
+ }
+
+ /**
+ * Whether the notification for this bubble is hidden from the shade.
+ */
+ public boolean isNotificationSuppressed() {
+ return (mFlags & Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION) != 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof BubbleInfo)) return false;
+ BubbleInfo bubble = (BubbleInfo) o;
+ return Objects.equals(mKey, bubble.mKey);
+ }
+
+ @Override
+ public int hashCode() {
+ return mKey.hashCode();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeString(mKey);
+ parcel.writeInt(mFlags);
+ parcel.writeString(mShortcutId);
+ parcel.writeTypedObject(mIcon, flags);
+ parcel.writeInt(mUserId);
+ parcel.writeString(mPackageName);
+ }
+
+ @NonNull
+ public static final Creator<BubbleInfo> CREATOR =
+ new Creator<BubbleInfo>() {
+ public BubbleInfo createFromParcel(Parcel source) {
+ return new BubbleInfo(source);
+ }
+
+ public BubbleInfo[] newArray(int size) {
+ return new BubbleInfo[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
new file mode 100644
index 0000000..f90591b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/RemovedBubble.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Represents a removed bubble, defining the key and reason the bubble was removed.
+ */
+public class RemovedBubble implements Parcelable {
+
+ private final String mKey;
+ private final int mRemovalReason;
+
+ public RemovedBubble(String key, int removalReason) {
+ mKey = key;
+ mRemovalReason = removalReason;
+ }
+
+ public RemovedBubble(Parcel parcel) {
+ mKey = parcel.readString();
+ mRemovalReason = parcel.readInt();
+ }
+
+ public String getKey() {
+ return mKey;
+ }
+
+ public int getRemovalReason() {
+ return mRemovalReason;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mKey);
+ dest.writeInt(mRemovalReason);
+ }
+
+ @NonNull
+ public static final Creator<RemovedBubble> CREATOR =
+ new Creator<RemovedBubble>() {
+ public RemovedBubble createFromParcel(Parcel source) {
+ return new RemovedBubble(source);
+ }
+ public RemovedBubble[] newArray(int size) {
+ return new RemovedBubble[size];
+ }
+ };
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index c5fc879..f2f30ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -1176,20 +1176,7 @@
final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
if (newDestinationBounds.equals(currentDestinationBounds)) return;
- if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- if (mWaitForFixedRotation) {
- // The new destination bounds are in next rotation (DisplayLayout has been rotated
- // in computeRotatedBounds). The animation runs in previous rotation so the end
- // bounds need to be transformed.
- final Rect displayBounds = mPipBoundsState.getDisplayBounds();
- final Rect rotatedEndBounds = new Rect(newDestinationBounds);
- rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
- animator.updateEndValue(rotatedEndBounds);
- } else {
- animator.updateEndValue(newDestinationBounds);
- }
- }
- animator.setDestinationBounds(newDestinationBounds);
+ updateAnimatorBounds(newDestinationBounds);
destinationBoundsOut.set(newDestinationBounds);
}
@@ -1201,7 +1188,17 @@
mPipAnimationController.getCurrentAnimator();
if (animator != null && animator.isRunning()) {
if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
- animator.updateEndValue(bounds);
+ if (mWaitForFixedRotation) {
+ // The new destination bounds are in next rotation (DisplayLayout has been
+ // rotated in computeRotatedBounds). The animation runs in previous rotation so
+ // the end bounds need to be transformed.
+ final Rect displayBounds = mPipBoundsState.getDisplayBounds();
+ final Rect rotatedEndBounds = new Rect(bounds);
+ rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
+ animator.updateEndValue(rotatedEndBounds);
+ } else {
+ animator.updateEndValue(bounds);
+ }
}
animator.setDestinationBounds(bounds);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index 73123b1..2c6ca1af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -18,7 +18,7 @@
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
-import android.app.ActivityManager;
+import android.annotation.IntDef;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -36,6 +36,7 @@
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SystemWindows;
@@ -61,13 +62,36 @@
private SurfaceControl mLeash;
private TvPipMenuView mPipMenuView;
private View mPipBackgroundView;
+ private boolean mMenuIsFocused;
- private boolean mMenuIsOpen;
- // User can actively move the PiP via the DPAD.
- private boolean mInMoveMode;
- // Used when only showing the move menu since we want to close the menu completely when
- // exiting the move menu instead of showing the regular button menu.
- private boolean mCloseAfterExitMoveMenu;
+ @TvPipMenuMode
+ private int mCurrentMenuMode = MODE_NO_MENU;
+ @TvPipMenuMode
+ private int mPrevMenuMode = MODE_NO_MENU;
+
+ @IntDef(prefix = { "MODE_" }, value = {
+ MODE_NO_MENU,
+ MODE_MOVE_MENU,
+ MODE_ALL_ACTIONS_MENU,
+ })
+ public @interface TvPipMenuMode {}
+
+ /**
+ * In this mode the PiP menu is not focused and no user controls are displayed.
+ */
+ static final int MODE_NO_MENU = 0;
+
+ /**
+ * In this mode the PiP menu is focused and the user can use the DPAD controls to move the PiP
+ * to a different position on the screen. We draw arrows in all possible movement directions.
+ */
+ static final int MODE_MOVE_MENU = 1;
+
+ /**
+ * In this mode the PiP menu is focused and we display an array of actions that the user can
+ * select. See {@link TvPipActionsProvider} for the types of available actions.
+ */
+ static final int MODE_ALL_ACTIONS_MENU = 2;
public TvPipMenuController(Context context, TvPipBoundsState tvPipBoundsState,
SystemWindows systemWindows, Handler mainHandler) {
@@ -143,11 +167,16 @@
"%s: Actions provider is not set", TAG);
return;
}
- mPipMenuView = new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ mPipMenuView = createTvPipMenuView();
setUpViewSurfaceZOrder(mPipMenuView, 1);
addPipMenuViewToSystemWindows(mPipMenuView, MENU_WINDOW_TITLE);
}
+ @VisibleForTesting
+ TvPipMenuView createTvPipMenuView() {
+ return new TvPipMenuView(mContext, mMainHandler, this, mTvPipActionsProvider);
+ }
+
private void attachPipBackgroundView() {
mPipBackgroundView = LayoutInflater.from(mContext)
.inflate(R.layout.tv_pip_menu_background, null);
@@ -188,37 +217,14 @@
void showMovementMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementMenuOnly()", TAG);
- setInMoveMode(true);
- if (mMenuIsOpen) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mCloseAfterExitMoveMenu = true;
- showMenuInternal();
- }
+ "%s: showMovementMenu()", TAG);
+ switchToMenuMode(MODE_MOVE_MENU);
}
@Override
public void showMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMenu()", TAG);
- setInMoveMode(false);
- mCloseAfterExitMoveMenu = false;
- showMenuInternal();
- }
-
- private void showMenuInternal() {
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = true;
- grantPipMenuFocus(true);
- if (mInMoveMode) {
- mPipMenuView.showMoveMenu(mTvPipBoundsState.getTvPipGravity());
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ false);
- }
- mPipMenuView.updateBounds(mTvPipBoundsState.getBounds());
+ switchToMenuMode(MODE_ALL_ACTIONS_MENU, true);
}
void onPipTransitionToTargetBoundsStarted(Rect targetBounds) {
@@ -228,9 +234,7 @@
}
void updateGravity(int gravity) {
- if (mInMoveMode) {
- mPipMenuView.showMovementHints(gravity);
- }
+ mPipMenuView.setPipGravity(gravity);
}
private Rect calculateMenuSurfaceBounds(Rect pipBounds) {
@@ -240,58 +244,7 @@
void closeMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: closeMenu()", TAG);
-
- if (mPipMenuView == null) {
- return;
- }
-
- mMenuIsOpen = false;
- mPipMenuView.hideAllUserControls();
- grantPipMenuFocus(false);
- mDelegate.onMenuClosed();
- }
-
- boolean isInMoveMode() {
- return mInMoveMode;
- }
-
- private void setInMoveMode(boolean moveMode) {
- if (mInMoveMode == moveMode) {
- return;
- }
- mInMoveMode = moveMode;
- if (mDelegate != null) {
- mDelegate.onInMoveModeChanged();
- }
- }
-
- @Override
- public boolean onExitMoveMode() {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onExitMoveMode - %b, close when exiting move menu: %b",
- TAG, mInMoveMode, mCloseAfterExitMoveMenu);
-
- if (mInMoveMode) {
- setInMoveMode(false);
- if (mCloseAfterExitMoveMenu) {
- mCloseAfterExitMoveMenu = false;
- closeMenu();
- } else {
- mPipMenuView.showButtonsMenu(/* exitingMoveMode= */ true);
- }
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPipMovement(int keycode) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: onPipMovement - %b", TAG, mInMoveMode);
- if (mInMoveMode) {
- mDelegate.movePip(keycode);
- }
- return mInMoveMode;
+ switchToMenuMode(MODE_NO_MENU);
}
@Override
@@ -422,13 +375,90 @@
getPipMenuLayoutParams(mContext, MENU_WINDOW_TITLE, menuBounds.width(),
menuBounds.height()));
if (mPipMenuView != null) {
- mPipMenuView.updateBounds(pipBounds);
+ mPipMenuView.setPipBounds(pipBounds);
}
}
+ // Start methods handling {@link TvPipMenuMode}
+
+ @VisibleForTesting
+ boolean isMenuOpen() {
+ return mCurrentMenuMode != MODE_NO_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInMoveMode() {
+ return mCurrentMenuMode == MODE_MOVE_MENU;
+ }
+
+ @VisibleForTesting
+ boolean isInAllActionsMode() {
+ return mCurrentMenuMode == MODE_ALL_ACTIONS_MENU;
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode) {
+ switchToMenuMode(menuMode, false);
+ }
+
+ private void switchToMenuMode(@TvPipMenuMode int menuMode, boolean resetMenu) {
+ // Note: we intentionally don't return early here, because the TvPipMenuView needs to
+ // refresh the Ui even if there is no menu mode change.
+ mPrevMenuMode = mCurrentMenuMode;
+ mCurrentMenuMode = menuMode;
+
+ ProtoLog.i(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: switchToMenuMode: setting mCurrentMenuMode=%s, mPrevMenuMode=%s", TAG,
+ getMenuModeString(), getMenuModeString(mPrevMenuMode));
+
+ updateUiOnNewMenuModeRequest(resetMenu);
+ updateDelegateOnNewMenuModeRequest();
+ }
+
+ private void updateUiOnNewMenuModeRequest(boolean resetMenu) {
+ if (mPipMenuView == null) return;
+
+ mPipMenuView.setPipGravity(mTvPipBoundsState.getTvPipGravity());
+ mPipMenuView.transitionToMenuMode(mCurrentMenuMode, resetMenu);
+ grantPipMenuFocus(mCurrentMenuMode != MODE_NO_MENU);
+ }
+
+ private void updateDelegateOnNewMenuModeRequest() {
+ if (mPrevMenuMode == mCurrentMenuMode) return;
+ if (mDelegate == null) return;
+
+ if (mPrevMenuMode == MODE_MOVE_MENU || isInMoveMode()) {
+ mDelegate.onInMoveModeChanged();
+ }
+
+ if (mCurrentMenuMode == MODE_NO_MENU) {
+ mDelegate.onMenuClosed();
+ }
+ }
+
+ @VisibleForTesting
+ String getMenuModeString() {
+ return getMenuModeString(mCurrentMenuMode);
+ }
+
+ static String getMenuModeString(@TvPipMenuMode int menuMode) {
+ switch(menuMode) {
+ case MODE_NO_MENU:
+ return "MODE_NO_MENU";
+ case MODE_MOVE_MENU:
+ return "MODE_MOVE_MENU";
+ case MODE_ALL_ACTIONS_MENU:
+ return "MODE_ALL_ACTIONS_MENU";
+ default:
+ return "Unknown";
+ }
+ }
+
+ // Start {@link TvPipMenuView.Delegate} methods
+
@Override
- public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onFocusTaskChanged", TAG);
+ public void onCloseEduText() {
+ mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
+ mDelegate.closeEduText();
}
@Override
@@ -439,9 +469,35 @@
}
@Override
- public void onCloseEduText() {
- mTvPipBoundsState.setPipMenuTemporaryDecorInsets(Insets.NONE);
- mDelegate.closeEduText();
+ public boolean onExitMoveMode() {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onExitMoveMode - mCurrentMenuMode=%s", TAG, getMenuModeString());
+
+ final int saveMenuMode = mCurrentMenuMode;
+ if (isInMoveMode()) {
+ switchToMenuMode(mPrevMenuMode);
+ }
+ return saveMenuMode == MODE_MOVE_MENU;
+ }
+
+ @Override
+ public boolean onPipMovement(int keycode) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipMovement - mCurrentMenuMode=%s", TAG, getMenuModeString());
+ if (isInMoveMode()) {
+ mDelegate.movePip(keycode);
+ }
+ return isInMoveMode();
+ }
+
+ @Override
+ public void onPipWindowFocusChanged(boolean focused) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: onPipWindowFocusChanged - focused=%b", TAG, focused);
+ mMenuIsFocused = focused;
+ if (!focused && isMenuOpen()) {
+ closeMenu();
+ }
}
interface Delegate {
@@ -455,6 +511,8 @@
}
private void grantPipMenuFocus(boolean grantFocus) {
+ if (mMenuIsFocused == grantFocus) return;
+
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: grantWindowFocus(%b)", TAG, grantFocus);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
index 56c602a..ccf65c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java
@@ -26,6 +26,9 @@
import static android.view.KeyEvent.KEYCODE_ENTER;
import static com.android.wm.shell.pip.tv.TvPipAction.ACTION_MOVE;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
import android.content.Context;
import android.graphics.Rect;
@@ -86,9 +89,9 @@
private final ImageView mArrowLeft;
private final TvWindowMenuActionButton mA11yDoneButton;
- private Rect mCurrentPipBounds;
- private boolean mMoveMenuIsVisible;
- private boolean mButtonMenuIsVisible;
+ private @TvPipMenuController.TvPipMenuMode int mCurrentMenuMode = MODE_NO_MENU;
+ private final Rect mCurrentPipBounds = new Rect();
+ private int mCurrentPipGravity;
private boolean mSwitchingOrientation;
private final AccessibilityManager mA11yManager;
@@ -172,7 +175,7 @@
return;
}
- if (mButtonMenuIsVisible) {
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) {
mSwitchingOrientation = true;
mActionButtonsRecyclerView.animate()
.alpha(0)
@@ -217,19 +220,14 @@
/**
* Also updates the button gravity.
*/
- void updateBounds(Rect updatedBounds) {
+ void setPipBounds(Rect updatedPipBounds) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: updateLayout, width: %s, height: %s", TAG, updatedBounds.width(),
- updatedBounds.height());
- mCurrentPipBounds = updatedBounds;
- updatePipFrameBounds();
- }
+ "%s: updateLayout, width: %s, height: %s", TAG, updatedPipBounds.width(),
+ updatedPipBounds.height());
+ if (updatedPipBounds.equals(mCurrentPipBounds)) return;
- Rect getPipMenuContainerBounds(Rect pipBounds) {
- final Rect menuUiBounds = new Rect(pipBounds);
- menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
- menuUiBounds.bottom += mEduTextDrawer.getHeight();
- return menuUiBounds;
+ mCurrentPipBounds.set(updatedPipBounds);
+ updatePipFrameBounds();
}
/**
@@ -264,12 +262,39 @@
}
}
- /**
- * @param gravity for the arrow hints
- */
- void showMoveMenu(int gravity) {
+ Rect getPipMenuContainerBounds(Rect pipBounds) {
+ final Rect menuUiBounds = new Rect(pipBounds);
+ menuUiBounds.inset(-mPipMenuOuterSpace, -mPipMenuOuterSpace);
+ menuUiBounds.bottom += mEduTextDrawer.getHeight();
+ return menuUiBounds;
+ }
+
+ void transitionToMenuMode(int menuMode, boolean resetMenu) {
+ switch (menuMode) {
+ case MODE_NO_MENU:
+ hideAllUserControls();
+ break;
+ case MODE_MOVE_MENU:
+ showMoveMenu();
+ break;
+ case MODE_ALL_ACTIONS_MENU:
+ showAllActionsMenu(resetMenu);
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Unknown TV PiP menu mode: "
+ + TvPipMenuController.getMenuModeString(mCurrentMenuMode));
+ }
+
+ mCurrentMenuMode = menuMode;
+ }
+
+ private void showMoveMenu() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: showMoveMenu()", TAG);
- showMovementHints(gravity);
+
+ if (mCurrentMenuMode == MODE_MOVE_MENU) return;
+
+ showMovementHints();
setMenuButtonsVisible(false);
setFrameHighlighted(true);
@@ -278,32 +303,38 @@
mEduTextDrawer.closeIfNeeded();
}
-
- void showButtonsMenu(boolean exitingMoveMode) {
+ private void showAllActionsMenu(boolean resetMenu) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showButtonsMenu(), exitingMoveMode %b", TAG, exitingMoveMode);
+ "%s: showAllActionsMenu(), resetMenu %b", TAG, resetMenu);
+
+ if (resetMenu) {
+ scrollToFirstAction();
+ }
+
+ if (mCurrentMenuMode == MODE_ALL_ACTIONS_MENU) return;
+
setMenuButtonsVisible(true);
hideMovementHints();
setFrameHighlighted(true);
animateAlphaTo(1f, mDimLayer);
mEduTextDrawer.closeIfNeeded();
- if (exitingMoveMode) {
- scrollAndRefocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE),
- /* alwaysScroll= */ false);
- } else {
- scrollAndRefocusButton(0, /* alwaysScroll= */ true);
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ refocusButton(mTvPipActionsProvider.getFirstIndexOfAction(ACTION_MOVE));
}
+
}
- private void scrollAndRefocusButton(int position, boolean alwaysScroll) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: scrollAndRefocusButton, target: %d", TAG, position);
-
- if (alwaysScroll || !refocusButton(position)) {
- mButtonLayoutManager.scrollToPositionWithOffset(position, 0);
- mActionButtonsRecyclerView.post(() -> refocusButton(position));
+ private void scrollToFirstAction() {
+ // Clearing the focus here is necessary to allow a smooth scroll even if the first action
+ // is currently not visible.
+ final View focusedChild = mActionButtonsRecyclerView.getFocusedChild();
+ if (focusedChild != null) {
+ focusedChild.clearFocus();
}
+
+ mButtonLayoutManager.scrollToPosition(0);
+ mActionButtonsRecyclerView.post(() -> refocusButton(0));
}
/**
@@ -311,6 +342,9 @@
* the view for the position not being available (scrolling beforehand will be necessary).
*/
private boolean refocusButton(int position) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "%s: refocusButton, position: %d", TAG, position);
+
View itemToFocus = mButtonLayoutManager.findViewByPosition(position);
if (itemToFocus != null) {
itemToFocus.requestFocus();
@@ -319,21 +353,29 @@
return itemToFocus != null;
}
- void hideAllUserControls() {
+ private void hideAllUserControls() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideAllUserControls()", TAG);
+
+ if (mCurrentMenuMode == MODE_NO_MENU) return;
+
setMenuButtonsVisible(false);
hideMovementHints();
setFrameHighlighted(false);
animateAlphaTo(0f, mDimLayer);
}
+ void setPipGravity(int gravity) {
+ mCurrentPipGravity = gravity;
+ if (mCurrentMenuMode == MODE_MOVE_MENU) {
+ showMovementHints();
+ }
+ }
+
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (!hasWindowFocus) {
- hideAllUserControls();
- }
+ mListener.onPipWindowFocusChanged(hasWindowFocus);
}
private void animateAlphaTo(float alpha, View view) {
@@ -399,15 +441,13 @@
/**
* Shows user hints for moving the PiP, e.g. arrows.
*/
- public void showMovementHints(int gravity) {
+ public void showMovementHints() {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: showMovementHints(), position: %s", TAG, Gravity.toString(gravity));
- mMoveMenuIsVisible = true;
-
- animateAlphaTo(checkGravity(gravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
- animateAlphaTo(checkGravity(gravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
- animateAlphaTo(checkGravity(gravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
- animateAlphaTo(checkGravity(gravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
+ "%s: showMovementHints(), position: %s", TAG, Gravity.toString(mCurrentPipGravity));
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.BOTTOM) ? 1f : 0f, mArrowUp);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.TOP) ? 1f : 0f, mArrowDown);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.RIGHT) ? 1f : 0f, mArrowLeft);
+ animateAlphaTo(checkGravity(mCurrentPipGravity, Gravity.LEFT) ? 1f : 0f, mArrowRight);
boolean a11yEnabled = mA11yManager.isEnabled();
setArrowA11yEnabled(mArrowUp, a11yEnabled, KEYCODE_DPAD_UP);
@@ -446,10 +486,7 @@
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: hideMovementHints()", TAG);
- if (!mMoveMenuIsVisible) {
- return;
- }
- mMoveMenuIsVisible = false;
+ if (mCurrentMenuMode != MODE_MOVE_MENU) return;
animateAlphaTo(0, mArrowUp);
animateAlphaTo(0, mArrowRight);
@@ -464,7 +501,6 @@
private void setMenuButtonsVisible(boolean visible) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: showUserActions: %b", TAG, visible);
- mButtonMenuIsVisible = visible;
animateAlphaTo(visible ? 1 : 0, mActionButtonsRecyclerView);
}
@@ -534,5 +570,11 @@
* @return whether pip movement was handled.
*/
boolean onPipMovement(int keycode);
+
+ /**
+ * Called when the TvPipMenuView loses focus. This also means that the TV PiP menu window
+ * has lost focus.
+ */
+ void onPipWindowFocusChanged(boolean focused);
}
-}
\ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index db75be7..5c64177 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -418,6 +418,13 @@
for (int i = 0; i < info.getChanges().size(); ++i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo != null
+ && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) {
+ // Tasks that are always on top (e.g. bubbles), will handle their own transition
+ // as they are on top of everything else. So cancel the merge here.
+ cancel();
+ return;
+ }
hasTaskChange = hasTaskChange || taskInfo != null;
final boolean isLeafTask = leafTaskFilter.test(change);
if (TransitionUtil.isOpeningType(change.getMode())) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
index bdda6a8..bfa6390 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
@@ -22,6 +22,8 @@
public class ShellSharedConstants {
// See IPip.aidl
public static final String KEY_EXTRA_SHELL_PIP = "extra_shell_pip";
+ // See IBubbles.aidl
+ public static final String KEY_EXTRA_SHELL_BUBBLES = "extra_shell_bubbles";
// See ISplitScreen.aidl
public static final String KEY_EXTRA_SHELL_SPLIT_SCREEN = "extra_shell_split_screen";
// See IOneHanded.aidl
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index e632b56..d25318d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -228,7 +228,7 @@
} else if ((mEndWidth > mStartWidth) == (mEndHeight > mStartHeight)
&& (mEndWidth != mStartWidth || mEndHeight != mStartHeight)) {
// Display resizes without rotation change.
- final float scale = Math.max((float) mEndWidth / mStartHeight,
+ final float scale = Math.max((float) mEndWidth / mStartWidth,
(float) mEndHeight / mStartHeight);
matrix.setScale(scale, scale);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index 2814ef9..e7d4598 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -18,14 +18,20 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.WindowConfiguration;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.RemoteException;
@@ -69,11 +75,7 @@
mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
mock(Choreographer.class));
spyOn(mCustomizeActivityAnimation);
- spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
- doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .load(any(), eq(false));
- doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
- .load(any(), eq(true));
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation);
}
RemoteAnimationTarget createAnimationTarget(boolean open) {
@@ -87,6 +89,12 @@
@Test
public void receiveFinishAfterInvoke() throws InterruptedException {
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(true));
+
mCustomizeActivityAnimation.prepareNextAnimation(
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -112,6 +120,12 @@
@Test
public void receiveFinishAfterCancel() throws InterruptedException {
+ spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(false));
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+ .loadAnimation(any(), eq(true));
+
mCustomizeActivityAnimation.prepareNextAnimation(
new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
final RemoteAnimationTarget close = createAnimationTarget(false);
@@ -152,4 +166,67 @@
verify(mCustomizeActivityAnimation).onGestureCommitted();
finishCalled.await(1, TimeUnit.SECONDS);
}
+
+ @Test
+ public void testLoadCustomAnimation() {
+ testLoadCustomAnimation(10, 20, 0);
+ }
+
+ @Test
+ public void testLoadCustomAnimationNoEnter() {
+ testLoadCustomAnimation(0, 10, 0);
+ }
+
+ @Test
+ public void testLoadWindowAnimations() {
+ testLoadCustomAnimation(0, 0, 30);
+ }
+
+ @Test
+ public void testCustomAnimationHigherThanWindowAnimations() {
+ testLoadCustomAnimation(10, 20, 30);
+ }
+
+ private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) {
+ final String testPackage = "TestPackage";
+ BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
+ .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN)
+ .setWindowAnimations(testPackage, windowAnimations);
+ final BackNavigationInfo.CustomAnimationInfo info = builder.build()
+ .getCustomAnimationInfo();
+
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAppTransitionAnimation(eq(testPackage), eq(exitResId));
+ doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation)
+ .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean());
+ doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader
+ .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean());
+
+ CustomizeActivityAnimation.AnimationLoadResult result =
+ mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info);
+
+ if (exitResId != 0) {
+ if (enterResId == 0) {
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+ never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId));
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation)
+ .loadDefaultAnimationAttr(anyInt(), anyBoolean());
+ } else {
+ assertEquals(result.mEnterAnimation, mMockOpenAnimation);
+ }
+ assertEquals(result.mBackgroundColor, Color.GREEN);
+ assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never())
+ .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+ } else if (windowAnimations != 0) {
+ verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation,
+ times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean());
+ assertEquals(result.mCloseAnimation, mMockCloseAnimation);
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
new file mode 100644
index 0000000..7c6037c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipMenuControllerTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.tv;
+
+import static android.view.KeyEvent.KEYCODE_DPAD_UP;
+
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_ALL_ACTIONS_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_MOVE_MENU;
+import static com.android.wm.shell.pip.tv.TvPipMenuController.MODE_NO_MENU;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.SystemWindows;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class TvPipMenuControllerTest extends ShellTestCase {
+ private static final int TEST_MOVE_KEYCODE = KEYCODE_DPAD_UP;
+
+ @Mock
+ private TvPipMenuController.Delegate mMockDelegate;
+ @Mock
+ private TvPipBoundsState mMockTvPipBoundsState;
+ @Mock
+ private SystemWindows mMockSystemWindows;
+ @Mock
+ private SurfaceControl mMockPipLeash;
+ @Mock
+ private Handler mMockHandler;
+ @Mock
+ private TvPipActionsProvider mMockActionsProvider;
+ @Mock
+ private TvPipMenuView mMockTvPipMenuView;
+
+ private TvPipMenuController mTvPipMenuController;
+
+ @Before
+ public void setUp() {
+ assumeTrue(isTelevision());
+
+ MockitoAnnotations.initMocks(this);
+
+ mTvPipMenuController = new TestTvPipMenuController();
+ mTvPipMenuController.setDelegate(mMockDelegate);
+ mTvPipMenuController.setTvPipActionsProvider(mMockActionsProvider);
+ mTvPipMenuController.attach(mMockPipLeash);
+ }
+
+ @Test
+ public void testMenuNotOpenByDefault() {
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToMoveMode() {
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testSwitch_FromNoMenuMode_ToAllActionsMode() {
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromMoveMode_ToAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ }
+
+ @Test
+ public void testSwitch_FromAllActionsMode_ToMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+ }
+
+ @Test
+ public void testCloseMenu_NoMenuMode() {
+ mTvPipMenuController.closeMenu();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveMode() {
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ closeMenuAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testCloseMenu_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ closeMenuAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_NoMenuMode() {
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testExitMoveMode_MoveMode() {
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+
+ }
+
+ @Test
+ public void testExitMoveMode_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onExitMoveMode();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+ }
+
+ @Test
+ public void testOnBackPress_NoMenuMode() {
+ mTvPipMenuController.onBackPress();
+ assertMenuIsOpen(false);
+ verify(mMockDelegate, never()).onMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveMode() {
+ showAndAssertMoveMenu();
+
+ pressBackAndAssertMenuClosed();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_MoveModeFollowedByAllActionsMode() {
+ showAndAssertMoveMenu();
+ showAndAssertAllActionsMenu();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnBackPress_AllActionsModeFollowedByMoveMode() {
+ showAndAssertAllActionsMenu();
+ showAndAssertMoveMenu();
+
+ mTvPipMenuController.onBackPress();
+ assertMenuIsInAllActionsMode();
+ verify(mMockDelegate, times(2)).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(false));
+
+ pressBackAndAssertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipMovement_NoMenuMode() {
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_MoveMode() {
+ showAndAssertMoveMenu();
+ assertPipMoveSuccessful(true, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ verify(mMockDelegate).movePip(eq(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipMovement_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ assertPipMoveSuccessful(false, mTvPipMenuController.onPipMovement(TEST_MOVE_KEYCODE));
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_NoMenuMode() {
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuIsOpen(false);
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_MoveMode() {
+ showAndAssertMoveMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ @Test
+ public void testOnPipWindowFocusChanged_AllActionsMode() {
+ showAndAssertAllActionsMenu();
+ mTvPipMenuController.onPipWindowFocusChanged(false);
+ assertMenuClosed();
+ }
+
+ private void showAndAssertMoveMenu() {
+ mTvPipMenuController.showMovementMenu();
+ assertMenuIsInMoveMode();
+ verify(mMockDelegate).onInMoveModeChanged();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_MOVE_MENU), eq(false));
+ }
+
+ private void showAndAssertAllActionsMenu() {
+ mTvPipMenuController.showMenu();
+ assertMenuIsInAllActionsMode();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_ALL_ACTIONS_MENU), eq(true));
+ }
+
+ private void closeMenuAndAssertMenuClosed() {
+ mTvPipMenuController.closeMenu();
+ assertMenuClosed();
+ }
+
+ private void pressBackAndAssertMenuClosed() {
+ mTvPipMenuController.onBackPress();
+ assertMenuClosed();
+ }
+
+ private void assertMenuClosed() {
+ assertMenuIsOpen(false);
+ verify(mMockDelegate).onMenuClosed();
+ verify(mMockTvPipMenuView).transitionToMenuMode(eq(MODE_NO_MENU), eq(false));
+ }
+
+ private void assertMenuIsOpen(boolean open) {
+ assertTrue("The TV PiP menu should " + (open ? "" : "not ") + "be open, but it"
+ + " is in mode " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isMenuOpen() == open);
+ }
+
+ private void assertMenuIsInMoveMode() {
+ assertTrue("Expected MODE_MOVE_MENU, but got " + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInMoveMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertMenuIsInAllActionsMode() {
+ assertTrue("Expected MODE_ALL_ACTIONS_MENU, but got "
+ + mTvPipMenuController.getMenuModeString(),
+ mTvPipMenuController.isInAllActionsMode());
+ assertMenuIsOpen(true);
+ }
+
+ private void assertPipMoveSuccessful(boolean expected, boolean actual) {
+ assertTrue("Should " + (expected ? "" : "not ") + "move PiP when the menu is in mode "
+ + mTvPipMenuController.getMenuModeString(), expected == actual);
+ }
+
+ private class TestTvPipMenuController extends TvPipMenuController {
+
+ TestTvPipMenuController() {
+ super(mContext, mMockTvPipBoundsState, mMockSystemWindows, mMockHandler);
+ }
+
+ @Override
+ TvPipMenuView createTvPipMenuView() {
+ return mMockTvPipMenuView;
+ }
+ }
+}
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 1d069b6..1e0c2cd 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -103,6 +103,8 @@
<string name="get_dialog_title_use_sign_in_for">Use your saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
<string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
+ <!-- This appears as the title of the dialog asking for user to make a choice from various previously saved credentials to sign in to the app. [CHAR LIMIT=200] -->
+ <string name="get_dialog_title_choose_option_for">Choose an option for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g>?</string>
<!-- This is a label for a button that links the user to different sign-in methods . [CHAR LIMIT=80] -->
<string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
<!-- This is a label for a button that links the user to different sign-in methods. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
index 8b95b5e..ba48f2b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetGenericCredentialComponents.kt
@@ -19,8 +19,31 @@
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.rememberSystemUiController
import com.android.credentialmanager.CredentialSelectorViewModel
+import com.android.credentialmanager.R
+import com.android.credentialmanager.common.BaseEntry
+import com.android.credentialmanager.common.ProviderActivityState
+import com.android.credentialmanager.common.ui.CredentialContainerCard
+import com.android.credentialmanager.common.ui.HeadlineIcon
+import com.android.credentialmanager.common.ui.HeadlineText
+import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
+import com.android.credentialmanager.common.ui.ModalBottomSheet
+import com.android.credentialmanager.common.ui.SheetContainerCard
+import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.internal.logging.UiEventLogger
+
@Composable
fun GetGenericCredentialScreen(
@@ -28,5 +51,102 @@
getCredentialUiState: GetCredentialUiState,
providerActivityLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
- // TODO(b/274129098): Implement Screen for mDocs
-}
\ No newline at end of file
+ val sysUiController = rememberSystemUiController()
+ setBottomSheetSystemBarsColor(sysUiController)
+ ModalBottomSheet(
+ sheetContent = {
+ when (viewModel.uiState.providerActivityState) {
+ ProviderActivityState.NOT_APPLICABLE -> {
+ PrimarySelectionCardGeneric(
+ requestDisplayInfo = getCredentialUiState.requestDisplayInfo,
+ providerDisplayInfo = getCredentialUiState.providerDisplayInfo,
+ providerInfoList = getCredentialUiState.providerInfoList,
+ onEntrySelected = viewModel::getFlowOnEntrySelected,
+ onLog = { viewModel.logUiEvent(it) },
+ )
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
+ }
+ ProviderActivityState.READY_TO_LAUNCH -> {
+ // Launch only once per providerActivityState change so that the provider
+ // UI will not be accidentally launched twice.
+ LaunchedEffect(viewModel.uiState.providerActivityState) {
+ viewModel.launchProviderUi(providerActivityLauncher)
+ }
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
+ }
+ ProviderActivityState.PENDING -> {
+ // Hide our content when the provider activity is active.
+ viewModel.uiMetrics.log(GetCredentialEvent
+ .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING)
+ }
+ }
+ },
+ onDismiss = viewModel::onUserCancel,
+ )
+}
+
+@Composable
+fun PrimarySelectionCardGeneric(
+ requestDisplayInfo: RequestDisplayInfo,
+ providerDisplayInfo: ProviderDisplayInfo,
+ providerInfoList: List<ProviderInfo>,
+ onEntrySelected: (BaseEntry) -> Unit,
+ onLog: @Composable (UiEventLogger.UiEventEnum) -> Unit,
+) {
+ val sortedUserNameToCredentialEntryList =
+ providerDisplayInfo.sortedUserNameToCredentialEntryList
+ SheetContainerCard {
+ // When only one provider (not counting the remote-only provider) exists, display that
+ // provider's icon + name up top.
+ if (providerInfoList.size <= 2) { // It's only possible to be the single provider case
+ // if we are started with no more than 2 providers.
+ val nonRemoteProviderList = providerInfoList.filter(
+ { it.credentialEntryList.isNotEmpty() || it.authenticationEntryList.isNotEmpty() }
+ )
+ if (nonRemoteProviderList.size == 1) {
+ val providerInfo = nonRemoteProviderList.firstOrNull() // First should always work
+ // but just to be safe.
+ if (providerInfo != null) {
+ item {
+ HeadlineIcon(
+ bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
+ tint = Color.Unspecified,
+ )
+ }
+ item { Divider(thickness = 4.dp, color = Color.Transparent) }
+ item { LargeLabelTextOnSurfaceVariant(text = providerInfo.displayName) }
+ item { Divider(thickness = 16.dp, color = Color.Transparent) }
+ }
+ }
+ }
+
+ item {
+ HeadlineText(
+ text = stringResource(
+ R.string.get_dialog_title_choose_option_for,
+ requestDisplayInfo.appName
+ ),
+ )
+ }
+ item { Divider(thickness = 24.dp, color = Color.Transparent) }
+ item {
+ CredentialContainerCard {
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ // Show max 4 entries in this primary page
+ sortedUserNameToCredentialEntryList.forEach {
+ // TODO(b/275375861): fallback UI merges entries by account names.
+ // Need a strategy to be able to show all entries.
+ CredentialEntryRow(
+ credentialEntryInfo = it.sortedCredentialEntryList.first(),
+ onEntrySelected = onEntrySelected,
+ enforceOneLine = true,
+ )
+ }
+ }
+ }
+ }
+ }
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
+}
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index f305fd3..e92157e 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -47,7 +47,7 @@
* Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
* subclasses.
*/
-@SupportedSourceVersion(SourceVersion.RELEASE_11)
+@SupportedSourceVersion(SourceVersion.RELEASE_17)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index a9d1464..f2f0fe9 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -114,6 +114,8 @@
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.AIRPLANE_MODE_ON,
Settings.Global.AIRPLANE_MODE_RADIOS,
+ Settings.Global.SATELLITE_MODE_RADIOS,
+ Settings.Global.SATELLITE_MODE_ENABLED,
Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS,
Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED,
Settings.Global.ALWAYS_FINISH_ACTIVITIES,
@@ -420,6 +422,7 @@
Settings.Global.RADIO_NFC,
Settings.Global.RADIO_WIFI,
Settings.Global.RADIO_WIMAX,
+ Settings.Global.RADIO_UWB,
Settings.Global.REMOVE_GUEST_ON_EXIT,
Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/README.md b/packages/SystemUI/accessibility/accessibilitymenu/README.md
new file mode 100644
index 0000000..b7fc363
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/README.md
@@ -0,0 +1,40 @@
+The Accessibility Menu is an accessibility service
+that presents a large on-screen menu to control your Android device.
+This service can be enabled from the Accessibility page in the Settings app.
+You can control gestures, hardware buttons, navigation, and more. From the menu, you can:
+
+- Take screenshots
+- Lock your screen
+- Open the device's voice assistant
+- Open Quick Settings and Notifications
+- Turn volume up or down
+- Turn brightness up or down
+
+The UI consists of a `ViewPager` populated by multiple pages of shortcut buttons.
+In the settings for the menu, there is an option to display the buttons in a 3x3 grid per page,
+or a 2x2 grid with larger buttons.
+
+Upon activation, most buttons will close the menu while performing their function.
+The exception to this are buttons that adjust a value, like volume or brightness,
+where the user is likely to want to press the button multiple times.
+In addition, touching other parts of the screen or locking the phone through other means
+should dismiss the menu.
+
+A majority of the shortcuts correspond directly to an existing accessibility service global action
+(see `AccessibilityService#performGlobalAction()` constants) that is performed when pressed.
+Shortcuts that navigate to a different menu, such as Quick Settings, use an intent to do so.
+Shortcuts that adjust brightness or volume interface directly with
+`DisplayManager` & `AudioManager` respectively.
+
+To add a new shortcut:
+
+1. Add a value for the new shortcut to the `ShortcutId` enum in `A11yMenuShortcut`.
+2. Put an entry for the enum value into the `sShortcutResource` `HashMap` in `A11yMenuShortcut`.
+This will require resources for a drawable icon, a color for the icon,
+the displayed name of the shortcut and the desired text-to-speech output.
+3. Add the enum value to the `SHORTCUT_LIST_DEFAULT` & `LARGE_SHORTCUT_LIST_DEFAULT` arrays
+in `A11yMenuOverlayLayout`.
+4. For functionality, add a code block to the if-else chain in
+`AccessibilityMenuService.handleClick()`, detailing the effect of the shortcut.
+If you don't want the shortcut to close the menu,
+include a return statement at the end of the code block.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 4e70455..59911b2 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -53,10 +53,6 @@
/** Returns true if the gesture should be rejected. */
boolean isFalseGesture();
- public boolean swipedFarEnough(float translation, float viewSize);
-
- public boolean swipedFastEnough(float translation, float velocity);
-
@ProvidesInterface(version = SnoozeOption.VERSION)
public interface SnoozeOption {
public static final int VERSION = 2;
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 2871cdf..4048a39 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -64,7 +64,8 @@
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
android:layout_gravity="bottom|start"
- android:scaleType="center"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
@@ -77,7 +78,8 @@
android:layout_height="@dimen/keyguard_affordance_fixed_height"
android:layout_width="@dimen/keyguard_affordance_fixed_width"
android:layout_gravity="bottom|end"
- android:scaleType="center"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
android:tint="?android:attr/textColorPrimary"
android:background="@drawable/keyguard_bottom_affordance_bg"
android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index bb82f91..11ec025 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -46,6 +46,7 @@
android:layout_toEndOf="@+id/snooze_option_default"
android:layout_centerVertical="true"
android:paddingTop="1dp"
+ android:importantForAccessibility="yes"
android:tint="#9E9E9E" />
<TextView
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 249fc86..0c0defa 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -793,6 +793,7 @@
<dimen name="keyguard_affordance_fixed_height">48dp</dimen>
<dimen name="keyguard_affordance_fixed_width">48dp</dimen>
<dimen name="keyguard_affordance_fixed_radius">24dp</dimen>
+ <dimen name="keyguard_affordance_fixed_padding">12dp</dimen>
<!-- Amount the button should shake when it's not long-pressed for long enough. -->
<dimen name="keyguard_affordance_shake_amplitude">8dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index f8cb38d..9f2333d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -277,6 +277,7 @@
@Override
public void onResume(int reason) {
mResumed = true;
+ reset();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 0c17489..68b40ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -271,6 +271,12 @@
}
@Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ reset();
+ }
+
+ @Override
public void onPause() {
super.onPause();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index fd47e39..f23bb0a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -38,12 +38,15 @@
private LockPatternUtils mLockPatternUtils;
private final FeatureFlags mFeatureFlags;
private static final int DEFAULT_PIN_LENGTH = 6;
+ private static final int MIN_FAILED_PIN_ATTEMPTS = 5;
private NumPadButton mBackspaceKey;
private View mOkButton = mView.findViewById(R.id.key_enter);
private int mUserId;
private long mPinLength;
+ private int mPasswordFailedAttempts;
+
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
@@ -82,8 +85,10 @@
protected void onUserInput() {
super.onUserInput();
if (isAutoConfirmation()) {
+ updateOKButtonVisibility();
updateBackSpaceVisibility();
- if (mPasswordEntry.getText().length() == mPinLength) {
+ if (mPasswordEntry.getText().length() == mPinLength
+ && mOkButton.getVisibility() == View.INVISIBLE) {
verifyPasswordAndUnlock();
}
}
@@ -101,7 +106,7 @@
mUserId = KeyguardUpdateMonitor.getCurrentUser();
mPinLength = mLockPatternUtils.getPinLength(mUserId);
mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
- mOkButton.setVisibility(isAutoConfirmation() ? View.INVISIBLE : View.VISIBLE);
+ updateOKButtonVisibility();
updateBackSpaceVisibility();
mPasswordEntry.setUsePinShapes(true);
mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
@@ -115,7 +120,18 @@
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
- //
+
+ /**
+ * Updates the visibility of the OK button for auto confirm feature
+ */
+ private void updateOKButtonVisibility() {
+ mPasswordFailedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(mUserId);
+ if (isAutoConfirmation() && mPasswordFailedAttempts < MIN_FAILED_PIN_ATTEMPTS) {
+ mOkButton.setVisibility(View.INVISIBLE);
+ } else {
+ mOkButton.setVisibility(View.VISIBLE);
+ }
+ }
/**
* Updates the visibility and the enabled state of the backspace.
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 68e1dd7..ddf1199 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -54,19 +54,23 @@
private final Factory mKeyguardSecurityViewControllerFactory;
private final FeatureFlags mFeatureFlags;
+ private final ViewMediatorCallback mViewMediatorCallback;
+
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
AsyncLayoutInflater asyncLayoutInflater,
KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ ViewMediatorCallback viewMediatorCallback) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mAsyncLayoutInflater = asyncLayoutInflater;
mFeatureFlags = featureFlags;
+ mViewMediatorCallback = viewMediatorCallback;
}
@Override
@@ -152,6 +156,7 @@
keyguardSecurityCallback);
childController.init();
mChildren.add(childController);
+ mViewMediatorCallback.setNeedsInput(childController.needsInput());
if (onViewInflatedListener != null) {
onViewInflatedListener.onViewInflated();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 1de3ddd..30321f7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2476,8 +2476,7 @@
* not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
*/
public boolean isUdfpsSupported() {
- return mAuthController.getUdfpsProps() != null
- && !mAuthController.getUdfpsProps().isEmpty();
+ return mAuthController.isUdfpsSupported();
}
/**
@@ -2492,8 +2491,7 @@
* not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
*/
public boolean isSfpsSupported() {
- return mAuthController.getSfpsProps() != null
- && !mAuthController.getSfpsProps().isEmpty();
+ return mAuthController.isSfpsSupported();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 64a9cc9..2503520 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -17,7 +17,6 @@
package com.android.systemui;
import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X;
-import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_Y;
import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat;
import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS;
@@ -92,7 +91,6 @@
private float mTouchSlopMultiplier;
private final Callback mCallback;
- private final int mSwipeDirection;
private final VelocityTracker mVelocityTracker;
private final FalsingManager mFalsingManager;
private final FeatureFlags mFeatureFlags;
@@ -141,12 +139,10 @@
private final ArrayMap<View, Animator> mDismissPendingMap = new ArrayMap<>();
public SwipeHelper(
- int swipeDirection, Callback callback, Resources resources,
- ViewConfiguration viewConfiguration, FalsingManager falsingManager,
- FeatureFlags featureFlags) {
+ Callback callback, Resources resources, ViewConfiguration viewConfiguration,
+ FalsingManager falsingManager, FeatureFlags featureFlags) {
mCallback = callback;
mHandler = new Handler();
- mSwipeDirection = swipeDirection;
mVelocityTracker = VelocityTracker.obtain();
mPagingTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
mSlopMultiplier = viewConfiguration.getScaledAmbiguousGestureMultiplier();
@@ -179,22 +175,22 @@
}
private float getPos(MotionEvent ev) {
- return mSwipeDirection == X ? ev.getX() : ev.getY();
+ return ev.getX();
}
private float getPerpendicularPos(MotionEvent ev) {
- return mSwipeDirection == X ? ev.getY() : ev.getX();
+ return ev.getY();
}
protected float getTranslation(View v) {
- return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
+ return v.getTranslationX();
}
private float getVelocity(VelocityTracker vt) {
- return mSwipeDirection == X ? vt.getXVelocity() :
- vt.getYVelocity();
+ return vt.getXVelocity();
}
+
protected Animator getViewTranslationAnimator(View view, float target,
AnimatorUpdateListener listener) {
@@ -209,8 +205,7 @@
protected Animator createTranslationAnimation(View view, float newPos,
AnimatorUpdateListener listener) {
- ObjectAnimator anim = ObjectAnimator.ofFloat(view,
- mSwipeDirection == X ? View.TRANSLATION_X : View.TRANSLATION_Y, newPos);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, newPos);
if (listener != null) {
anim.addUpdateListener(listener);
@@ -220,18 +215,13 @@
}
protected void setTranslation(View v, float translate) {
- if (v == null) {
- return;
- }
- if (mSwipeDirection == X) {
+ if (v != null) {
v.setTranslationX(translate);
- } else {
- v.setTranslationY(translate);
}
}
protected float getSize(View v) {
- return mSwipeDirection == X ? v.getMeasuredWidth() : v.getMeasuredHeight();
+ return v.getMeasuredWidth();
}
public void setMinSwipeProgress(float minSwipeProgress) {
@@ -426,15 +416,12 @@
float newPos;
boolean isLayoutRtl = animView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
- // if we use the Menu to dismiss an item in landscape, animate up
- boolean animateUpForMenu = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
- && mSwipeDirection == Y;
// if the language is rtl we prefer swiping to the left
boolean animateLeftForRtl = velocity == 0 && (getTranslation(animView) == 0 || isDismissAll)
&& isLayoutRtl;
boolean animateLeft = (Math.abs(velocity) > getEscapeVelocity() && velocity < 0) ||
(getTranslation(animView) < 0 && !isDismissAll);
- if (animateLeft || animateLeftForRtl || animateUpForMenu) {
+ if (animateLeft || animateLeftForRtl) {
newPos = -getTotalTranslationLength(animView);
} else {
newPos = getTotalTranslationLength(animView);
@@ -576,8 +563,7 @@
startVelocity,
mSnapBackSpringConfig);
}
- return PhysicsAnimator.getInstance(target).spring(
- mSwipeDirection == X ? TRANSLATION_X : TRANSLATION_Y, toPosition, startVelocity,
+ return PhysicsAnimator.getInstance(target).spring(TRANSLATION_X, toPosition, startVelocity,
mSnapBackSpringConfig);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 705fc8c..92344db 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -984,6 +984,36 @@
return mSidefpsProps;
}
+ /**
+ * @return true if udfps HW is supported on this device. Can return true even if the user has
+ * not enrolled udfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isUdfpsSupported() {
+ return getUdfpsProps() != null && !getUdfpsProps().isEmpty();
+ }
+
+ /**
+ * @return true if sfps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isSfpsSupported() {
+ return getSfpsProps() != null && !getSfpsProps().isEmpty();
+ }
+
+ /**
+ * @return true if rear fps HW is supported on this device. Can return true even if the user has
+ * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+ */
+ public boolean isRearFpsSupported() {
+ for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+ if (prop.sensorType == TYPE_REAR) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
private String getErrorString(@Modality int modality, int error, int vendorCode) {
switch (modality) {
case TYPE_FACE:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index c98a62f..eb5d23a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -59,8 +59,6 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -90,7 +88,6 @@
@Main private val handler: Handler,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@Application private val scope: CoroutineScope,
- private val featureFlags: FeatureFlags,
dumpManager: DumpManager
) : Dumpable {
private val requests: HashSet<SideFpsUiRequestSource> = HashSet()
@@ -191,14 +188,12 @@
private fun listenForAlternateBouncerVisibility() {
alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
- if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) {
- scope.launch {
- alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
- if (isVisible) {
- show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
- } else {
- hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
- }
+ scope.launch {
+ alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
+ if (isVisible) {
+ show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
+ } else {
+ hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
index 231e7a4..3e7d81a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -42,7 +42,6 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
@@ -82,8 +81,6 @@
) {
private val useExpandedOverlay: Boolean =
featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
- private val isModernAlternateBouncerEnabled: Boolean =
- featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
private var showingUdfpsBouncer = false
private var udfpsRequested = false
private var qsExpansion = 0f
@@ -107,7 +104,7 @@
)
}
}
- private var inputBouncerExpansion = 0f // only used for modernBouncer
+ private var inputBouncerExpansion = 0f
private val stateListener: StatusBarStateController.StateListener =
object : StatusBarStateController.StateListener {
@@ -251,7 +248,7 @@
// that may make the view visible again.
repeatOnLifecycle(Lifecycle.State.CREATED) {
listenForBouncerExpansion(this)
- if (isModernAlternateBouncerEnabled) listenForAlternateBouncerVisibility(this)
+ listenForAlternateBouncerVisibility(this)
}
}
}
@@ -295,7 +292,6 @@
view.updatePadding()
updateAlpha()
updatePauseAuth()
- keyguardViewManager.setLegacyAlternateBouncer(legacyAlternateBouncer)
keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI)
lockScreenShadeTransitionController.udfpsKeyguardViewController = this
activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
@@ -309,7 +305,6 @@
faceDetectRunning = false
keyguardStateController.removeCallback(keyguardStateControllerCallback)
statusBarStateController.removeCallback(stateListener)
- keyguardViewManager.removeLegacyAlternateBouncer(legacyAlternateBouncer)
keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
configurationController.removeCallback(configurationListener)
@@ -323,7 +318,6 @@
override fun dump(pw: PrintWriter, args: Array<String>) {
super.dump(pw, args)
- pw.println("isModernAlternateBouncerEnabled=$isModernAlternateBouncerEnabled")
pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
pw.println(
"altBouncerInteractor#isAlternateBouncerVisible=" +
@@ -473,22 +467,6 @@
private fun updateScaleFactor() {
udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
}
-
- private val legacyAlternateBouncer: LegacyAlternateBouncer =
- object : LegacyAlternateBouncer {
- override fun showAlternateBouncer(): Boolean {
- return showUdfpsBouncer(true)
- }
-
- override fun hideAlternateBouncer(): Boolean {
- return showUdfpsBouncer(false)
- }
-
- override fun isShowingAlternateBouncer(): Boolean {
- return showingUdfpsBouncer
- }
- }
-
companion object {
const val TAG = "UdfpsKeyguardViewController"
}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4fe219d..79a51d6 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -169,13 +169,6 @@
@JvmField
val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
- /**
- * Whether to use the new alternate bouncer architecture, a refactor of and eventual replacement
- * of the Alternate/Authentication Bouncer. No visual UI changes.
- */
- // TODO(b/260619425): Tracking Bug
- @JvmField val MODERN_ALTERNATE_BOUNCER = releasedFlag(219, "modern_alternate_bouncer")
-
/** Flag to control the migration of face auth to modern architecture. */
// TODO(b/262838215): Tracking bug
@JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -191,7 +184,7 @@
// flag for controlling auto pin confirmation and material u shapes in bouncer
@JvmField
val AUTO_PIN_CONFIRMATION =
- unreleasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+ releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation", teamfood = true)
// TODO(b/262859270): Tracking Bug
@JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
@@ -226,12 +219,16 @@
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/272091103): Tracking Bug
@JvmField
- val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = false)
+ val ASYNC_INFLATE_BOUNCER = unreleasedFlag(229, "async_inflate_bouncer", teamfood = true)
/** Whether to inflate the bouncer view on a background thread. */
// TODO(b/273341787): Tracking Bug
@JvmField
- val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard")
+ val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true)
+
+ /** Whether to use a new data source for intents to run on keyguard dismissal. */
+ @JvmField
+ val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
// 300 - power menu
// TODO(b/254512600): Tracking Bug
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 7c46684..4fa56ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -20,6 +20,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -29,6 +30,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
@@ -37,6 +39,17 @@
interface DeviceEntryFingerprintAuthRepository {
/** Whether the device entry fingerprint auth is locked out. */
val isLockedOut: StateFlow<Boolean>
+
+ /**
+ * Whether the fingerprint sensor is currently listening, this doesn't mean that the user is
+ * actively authenticating.
+ */
+ val isRunning: Flow<Boolean>
+
+ /**
+ * Fingerprint sensor type present on the device, null if fingerprint sensor is not available.
+ */
+ val availableFpSensorType: BiometricType?
}
/**
@@ -50,6 +63,7 @@
class DeviceEntryFingerprintAuthRepositoryImpl
@Inject
constructor(
+ val authController: AuthController,
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@Application scope: CoroutineScope,
dumpManager: DumpManager,
@@ -63,6 +77,12 @@
pw.println("isLockedOut=${isLockedOut.value}")
}
+ override val availableFpSensorType: BiometricType?
+ get() =
+ if (authController.isUdfpsSupported) BiometricType.UNDER_DISPLAY_FINGERPRINT
+ else if (authController.isSfpsSupported) BiometricType.SIDE_FINGERPRINT
+ else if (authController.isRearFpsSupported) BiometricType.REAR_FINGERPRINT else null
+
override val isLockedOut: StateFlow<Boolean> =
conflatedCallbackFlow {
val sendLockoutUpdate =
@@ -89,6 +109,32 @@
}
.stateIn(scope, started = SharingStarted.Eagerly, initialValue = false)
+ override val isRunning: Flow<Boolean>
+ get() = conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onBiometricRunningStateChanged(
+ running: Boolean,
+ biometricSourceType: BiometricSourceType?
+ ) {
+ if (biometricSourceType == BiometricSourceType.FINGERPRINT) {
+ trySendWithFailureLogging(
+ running,
+ TAG,
+ "Fingerprint running state changed"
+ )
+ }
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ trySendWithFailureLogging(
+ keyguardUpdateMonitor.isFingerprintDetectionRunning,
+ TAG,
+ "Initial fingerprint running state"
+ )
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+
companion object {
const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
index aad4a2d..9b94cdb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt
@@ -16,15 +16,11 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
@@ -41,13 +37,7 @@
private val biometricSettingsRepository: BiometricSettingsRepository,
private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
private val systemClock: SystemClock,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- featureFlags: FeatureFlags,
) {
- val isModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)
- var legacyAlternateBouncer: LegacyAlternateBouncer? = null
- var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE
-
var receivedDownTouch = false
val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
@@ -68,21 +58,8 @@
* @return whether alternateBouncer is visible
*/
fun show(): Boolean {
- return when {
- isModernAlternateBouncerEnabled -> {
- bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
- isVisibleState()
- }
- canShowAlternateBouncerForFingerprint() -> {
- if (legacyAlternateBouncer?.showAlternateBouncer() == true) {
- legacyAlternateBouncerVisibleTime = systemClock.uptimeMillis()
- true
- } else {
- false
- }
- }
- else -> false
- }
+ bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint())
+ return isVisibleState()
}
/**
@@ -94,21 +71,13 @@
*/
fun hide(): Boolean {
receivedDownTouch = false
- return if (isModernAlternateBouncerEnabled) {
- val wasAlternateBouncerVisible = isVisibleState()
- bouncerRepository.setAlternateVisible(false)
- wasAlternateBouncerVisible && !isVisibleState()
- } else {
- legacyAlternateBouncer?.hideAlternateBouncer() ?: false
- }
+ val wasAlternateBouncerVisible = isVisibleState()
+ bouncerRepository.setAlternateVisible(false)
+ return wasAlternateBouncerVisible && !isVisibleState()
}
fun isVisibleState(): Boolean {
- return if (isModernAlternateBouncerEnabled) {
- bouncerRepository.alternateBouncerVisible.value
- } else {
- legacyAlternateBouncer?.isShowingAlternateBouncer ?: false
- }
+ return bouncerRepository.alternateBouncerVisible.value
}
fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
@@ -116,18 +85,13 @@
}
fun canShowAlternateBouncerForFingerprint(): Boolean {
- return if (isModernAlternateBouncerEnabled) {
- bouncerRepository.alternateBouncerUIAvailable.value &&
- biometricSettingsRepository.isFingerprintEnrolled.value &&
- biometricSettingsRepository.isStrongBiometricAllowed.value &&
- biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
- !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
- !keyguardStateController.isUnlocked &&
- !statusBarStateController.isDozing
- } else {
- legacyAlternateBouncer != null &&
- keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true)
- }
+ return bouncerRepository.alternateBouncerUIAvailable.value &&
+ biometricSettingsRepository.isFingerprintEnrolled.value &&
+ biometricSettingsRepository.isStrongBiometricAllowed.value &&
+ biometricSettingsRepository.isFingerprintEnabledByDevicePolicy.value &&
+ !deviceEntryFingerprintAuthRepository.isLockedOut.value &&
+ !keyguardStateController.isUnlocked &&
+ !statusBarStateController.isDozing
}
/**
@@ -135,12 +99,8 @@
* alternate bouncer and show the primary bouncer.
*/
fun hasAlternateBouncerShownWithMinTime(): Boolean {
- return if (isModernAlternateBouncerEnabled) {
- (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
- MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
- } else {
- systemClock.uptimeMillis() - legacyAlternateBouncerVisibleTime > 200
- }
+ return (systemClock.uptimeMillis() - bouncerRepository.lastAlternateBouncerVisibleTime) >
+ MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS
}
private fun maybeHide() {
@@ -151,6 +111,5 @@
companion object {
private const val MIN_VISIBILITY_DURATION_UNTIL_TOUCHES_DISMISS_ALTERNATE_BOUNCER_MS = 200L
- private const val NOT_VISIBLE = -1L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 35819e3..9606bcf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -78,7 +78,7 @@
private static final boolean DEBUG = true;
private static final int HANDLE_BROADCAST_FAILED_DELAY = 3000;
- private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+ protected final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
private final RecyclerView.LayoutManager mLayoutManager;
final Context mContext;
@@ -102,11 +102,13 @@
private int mListMaxHeight;
private int mItemHeight;
private WallpaperColors mWallpaperColors;
- private Executor mExecutor;
private boolean mShouldLaunchLeBroadcastDialog;
+ private boolean mIsLeBroadcastCallbackRegistered;
MediaOutputBaseAdapter mAdapter;
+ protected Executor mExecutor;
+
private final ViewTreeObserver.OnGlobalLayoutListener mDeviceListLayoutListener = () -> {
ViewGroup.LayoutParams params = mDeviceListLayout.getLayoutParams();
int totalItemsHeight = mAdapter.getItemCount() * mItemHeight;
@@ -274,17 +276,19 @@
public void onStart() {
super.onStart();
mMediaOutputController.start(this);
- if(isBroadcastSupported()) {
- mMediaOutputController.registerLeBroadcastServiceCallBack(mExecutor,
+ if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) {
+ mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor,
mBroadcastCallback);
+ mIsLeBroadcastCallbackRegistered = true;
}
}
@Override
public void onStop() {
super.onStop();
- if(isBroadcastSupported()) {
- mMediaOutputController.unregisterLeBroadcastServiceCallBack(mBroadcastCallback);
+ if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+ mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
+ mIsLeBroadcastCallbackRegistered = false;
}
mMediaOutputController.stop();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index 12d6b7c..f0ff140 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -17,6 +17,10 @@
package com.android.systemui.media.dialog;
import android.app.AlertDialog;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
@@ -34,8 +38,11 @@
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.IconCompat;
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.qrcode.QrCodeGenerator;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastSender;
@@ -49,7 +56,7 @@
*/
@SysUISingleton
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
- private static final String TAG = "BroadcastDialog";
+ private static final String TAG = "MediaOutputBroadcastDialog";
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
@@ -66,6 +73,7 @@
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+
private TextWatcher mTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
@@ -105,6 +113,79 @@
}
};
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
+
+ private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new BluetoothLeBroadcastAssistant.Callback() {
+ @Override
+ public void onSearchStarted(int reason) {
+ Log.d(TAG, "Assistant-onSearchStarted: " + reason);
+ }
+
+ @Override
+ public void onSearchStartFailed(int reason) {
+ Log.d(TAG, "Assistant-onSearchStartFailed: " + reason);
+ }
+
+ @Override
+ public void onSearchStopped(int reason) {
+ Log.d(TAG, "Assistant-onSearchStopped: " + reason);
+ }
+
+ @Override
+ public void onSearchStopFailed(int reason) {
+ Log.d(TAG, "Assistant-onSearchStopFailed: " + reason);
+ }
+
+ @Override
+ public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {
+ Log.d(TAG, "Assistant-onSourceFound:");
+ }
+
+ @Override
+ public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
+ Log.d(TAG, "Assistant-onSourceAdded: Device: " + sink
+ + ", sourceId: " + sourceId);
+ mMainThreadHandler.post(() -> refreshUi());
+ }
+
+ @Override
+ public void onSourceAddFailed(@NonNull BluetoothDevice sink,
+ @NonNull BluetoothLeBroadcastMetadata source, int reason) {
+ Log.d(TAG, "Assistant-onSourceAddFailed: Device: " + sink);
+ }
+
+ @Override
+ public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceModified:");
+ }
+
+ @Override
+ public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceModifyFailed:");
+ }
+
+ @Override
+ public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceRemoved:");
+ }
+
+ @Override
+ public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
+ int reason) {
+ Log.d(TAG, "Assistant-onSourceRemoveFailed:");
+ }
+
+ @Override
+ public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
+ @NonNull BluetoothLeBroadcastReceiveState state) {
+ Log.d(TAG, "Assistant-onReceiveStateChanged:");
+ }
+ };
+
static final int METADATA_BROADCAST_NAME = 0;
static final int METADATA_BROADCAST_CODE = 1;
@@ -131,6 +212,27 @@
}
@Override
+ public void onStart() {
+ super.onStart();
+ if (!mIsLeBroadcastAssistantCallbackRegistered) {
+ mIsLeBroadcastAssistantCallbackRegistered = true;
+ mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor,
+ mBroadcastAssistantCallback);
+ }
+ connectBroadcastWithActiveDevice();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mIsLeBroadcastAssistantCallbackRegistered) {
+ mIsLeBroadcastAssistantCallbackRegistered = false;
+ mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback(
+ mBroadcastAssistantCallback);
+ }
+ }
+
+ @Override
int getHeaderIconRes() {
return 0;
}
@@ -224,6 +326,7 @@
mCurrentBroadcastCode = getBroadcastMetadataInfo(METADATA_BROADCAST_CODE);
mBroadcastName.setText(mCurrentBroadcastName);
mBroadcastCode.setText(mCurrentBroadcastCode);
+ refresh(false);
}
private void inflateBroadcastInfoArea() {
@@ -233,7 +336,7 @@
private void setQrCodeView() {
//get the Metadata, and convert to BT QR code format.
- String broadcastMetadata = getBroadcastMetadata();
+ String broadcastMetadata = getLocalBroadcastMetadataQrCodeString();
if (broadcastMetadata.isEmpty()) {
//TDOD(b/226708424) Error handling for unable to generate the QR code bitmap
return;
@@ -249,6 +352,33 @@
}
}
+ void connectBroadcastWithActiveDevice() {
+ //get the Metadata, and convert to BT QR code format.
+ BluetoothLeBroadcastMetadata broadcastMetadata = getBroadcastMetadata();
+ if (broadcastMetadata == null) {
+ Log.e(TAG, "Error: There is no broadcastMetadata.");
+ return;
+ }
+ MediaDevice mediaDevice = mMediaOutputController.getCurrentConnectedMediaDevice();
+ if (mediaDevice == null || !(mediaDevice instanceof BluetoothMediaDevice)
+ || !mediaDevice.isBLEDevice()) {
+ Log.e(TAG, "Error: There is no active BT LE device.");
+ return;
+ }
+ BluetoothDevice sink = ((BluetoothMediaDevice) mediaDevice).getCachedDevice().getDevice();
+ Log.d(TAG, "The broadcastMetadata broadcastId: " + broadcastMetadata.getBroadcastId()
+ + ", the device: " + sink.getAnonymizedAddress());
+
+ if (mMediaOutputController.isThereAnyBroadcastSourceIntoSinkDevice(sink)) {
+ Log.d(TAG, "The sink device has the broadcast source now.");
+ return;
+ }
+ if (!mMediaOutputController.addSourceIntoSinkDeviceWithBluetoothLeAssistant(sink,
+ broadcastMetadata, /*isGroupOp=*/ true)) {
+ Log.e(TAG, "Error: Source add failed");
+ }
+ }
+
private void updateBroadcastCodeVisibility() {
mBroadcastCode.setTransformationMethod(
mIsPasswordHide ? HideReturnsTransformationMethod.getInstance()
@@ -282,7 +412,11 @@
mAlertDialog.show();
}
- private String getBroadcastMetadata() {
+ private String getLocalBroadcastMetadataQrCodeString() {
+ return mMediaOutputController.getLocalBroadcastMetadataQrCodeString();
+ }
+
+ private BluetoothLeBroadcastMetadata getBroadcastMetadata() {
return mMediaOutputController.getBroadcastMetadata();
}
@@ -314,6 +448,17 @@
}
@Override
+ public boolean isBroadcastSupported() {
+ boolean isBluetoothLeDevice = false;
+ if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
+ mMediaOutputController.getCurrentConnectedMediaDevice());
+ }
+
+ return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+ }
+
+ @Override
public void handleLeBroadcastStarted() {
mRetryCount = 0;
if (mAlertDialog != null) {
@@ -332,6 +477,7 @@
@Override
public void handleLeBroadcastMetadataChanged() {
+ Log.d(TAG, "handleLeBroadcastMetadataChanged:");
refreshUi();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index f3f17d1..9ebc8e4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -25,7 +25,11 @@
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.WallpaperColors;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
@@ -66,6 +70,7 @@
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
@@ -1049,7 +1054,7 @@
ALLOWLIST_DURATION_MS);
}
- String getBroadcastMetadata() {
+ String getLocalBroadcastMetadataQrCodeString() {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
if (broadcast == null) {
@@ -1061,6 +1066,17 @@
return metadata != null ? metadata.convertToQrCodeString() : "";
}
+ BluetoothLeBroadcastMetadata getBroadcastMetadata() {
+ LocalBluetoothLeBroadcast broadcast =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
+ if (broadcast == null) {
+ Log.d(TAG, "getBroadcastMetadata: LE Audio Broadcast is null");
+ return null;
+ }
+
+ return broadcast.getLatestBluetoothLeBroadcastMetadata();
+ }
+
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
final List<String> features = device.getFeatures();
return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
@@ -1121,7 +1137,7 @@
return true;
}
- void registerLeBroadcastServiceCallBack(
+ void registerLeBroadcastServiceCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothLeBroadcast.Callback callback) {
LocalBluetoothLeBroadcast broadcast =
@@ -1130,10 +1146,11 @@
Log.d(TAG, "The broadcast profile is null");
return;
}
+ Log.d(TAG, "Register LE broadcast callback");
broadcast.registerServiceCallBack(executor, callback);
}
- void unregisterLeBroadcastServiceCallBack(
+ void unregisterLeBroadcastServiceCallback(
@NonNull BluetoothLeBroadcast.Callback callback) {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
@@ -1141,9 +1158,59 @@
Log.d(TAG, "The broadcast profile is null");
return;
}
+ Log.d(TAG, "Unregister LE broadcast callback");
broadcast.unregisterServiceCallBack(callback);
}
+ boolean isThereAnyBroadcastSourceIntoSinkDevice(BluetoothDevice sink) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return false;
+ }
+ List<BluetoothLeBroadcastReceiveState> sourceList = assistant.getAllSources(sink);
+ Log.d(TAG, "isThereAnyBroadcastSourceIntoSinkDevice: List size: " + sourceList.size());
+ return !sourceList.isEmpty();
+ }
+
+ boolean addSourceIntoSinkDeviceWithBluetoothLeAssistant(BluetoothDevice sink,
+ BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return false;
+ }
+ assistant.addSource(sink, metadata, isGroupOp);
+ return true;
+ }
+
+ void registerLeBroadcastAssistantServiceCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return;
+ }
+ Log.d(TAG, "Register LE broadcast assistant callback");
+ assistant.registerServiceCallBack(executor, callback);
+ }
+
+ void unregisterLeBroadcastAssistantServiceCallback(
+ @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+ LocalBluetoothLeBroadcastAssistant assistant =
+ mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastAssistantProfile();
+ if (assistant == null) {
+ Log.d(TAG, "The broadcast assistant profile is null");
+ return;
+ }
+ Log.d(TAG, "Unregister LE broadcast assistant callback");
+ assistant.unregisterServiceCallBack(callback);
+ }
+
private boolean isPlayBackInfoLocal() {
return mMediaController != null
&& mMediaController.getPlaybackInfo() != null
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index 3a4ea3e..e352c61 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -182,6 +182,26 @@
interactionState = null
true
}
+ MotionEvent.ACTION_POINTER_UP -> {
+ val removedPointerId = event.getPointerId(event.actionIndex)
+ if (removedPointerId == interactionState?.pointerId && event.pointerCount > 1) {
+ // We removed the original pointer but there must be another pointer because the
+ // gesture is still ongoing. Let's switch to that pointer.
+ interactionState =
+ event.firstUnremovedPointerId(removedPointerId)?.let { replacementPointerId
+ ->
+ interactionState?.copy(
+ pointerId = replacementPointerId,
+ // We want to update the currentY of our state so that the
+ // transition to the next pointer doesn't report a big jump between
+ // the Y coordinate of the removed pointer and the Y coordinate of
+ // the replacement pointer.
+ currentY = event.getY(replacementPointerId),
+ )
+ }
+ }
+ true
+ }
MotionEvent.ACTION_CANCEL -> {
if (isDraggingShade()) {
// Our drag gesture was canceled by the system. This happens primarily in one of
@@ -219,4 +239,17 @@
private fun isDraggingShade(): Boolean {
return interactionState?.isDraggingShade ?: false
}
+
+ /**
+ * Returns the index of the first pointer that is not [removedPointerId] or `null`, if there is
+ * no other pointer.
+ */
+ private fun MotionEvent.firstUnremovedPointerId(removedPointerId: Int): Int? {
+ return (0 until pointerCount)
+ .firstOrNull { pointerIndex ->
+ val pointerId = getPointerId(pointerIndex)
+ pointerId != removedPointerId
+ }
+ ?.let { pointerIndex -> getPointerId(pointerIndex) }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 0d5a3fd..a29eb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -56,7 +56,8 @@
internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L
private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L
private const val MIN_DURATION_CANCELLED_ANIMATION = 200L
-private const val MIN_DURATION_COMMITTED_ANIMATION = 120L
+private const val MIN_DURATION_COMMITTED_ANIMATION = 80L
+private const val MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION = 120L
private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L
private const val MIN_DURATION_FLING_ANIMATION = 160L
@@ -918,7 +919,7 @@
if (previousState == GestureState.FLUNG) {
updateRestingArrowDimens()
mainHandler.postDelayed(onEndSetGoneStateListener.runnable,
- MIN_DURATION_COMMITTED_ANIMATION)
+ MIN_DURATION_COMMITTED_AFTER_FLING_ANIMATION)
} else {
mView.popScale(POP_ON_FLING_SCALE)
mainHandler.postDelayed(onAlphaEndSetGoneStateListener.runnable,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 35b6c15..6ce6f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -219,7 +219,7 @@
height = getDimen(R.dimen.navigation_edge_active_background_height),
edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners),
- widthSpring = createSpring(650f, 0.75f),
+ widthSpring = createSpring(850f, 0.75f),
heightSpring = createSpring(10000f, 1f),
edgeCornerRadiusSpring = createSpring(600f, 0.36f),
farCornerRadiusSpring = createSpring(2500f, 0.855f),
@@ -274,8 +274,8 @@
farCornerRadiusSpring = flungCommittedFarCornerSpring,
alphaSpring = createSpring(1400f, 1f),
),
- scale = 0.85f,
- scaleSpring = createSpring(6000f, 1f),
+ scale = 0.86f,
+ scaleSpring = createSpring(5700f, 1f),
)
flungIndicator = committedIndicator.copy(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 27fe747..a352f23 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -234,6 +234,14 @@
})
}
+ fun logNoPulsingNotificationHidden(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ }, {
+ "No pulsing: notification hidden on lock screen: $str1"
+ })
+ }
+
fun logNoPulsingNotImportant(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
str1 = entry.logKey
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
index bfb6416..9a1747a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProvider.java
@@ -61,6 +61,12 @@
*/
NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(false),
/**
+ * Notification should not FSI due to having suppressive BubbleMetadata. This blocks a
+ * potentially malicious use of flags that previously allowed apps to escalate a HUN to an
+ * FSI even while the device was unlocked.
+ */
+ NO_FSI_SUPPRESSIVE_BUBBLE_METADATA(false),
+ /**
* Device screen is off, so the FSI should launch.
*/
FSI_DEVICE_NOT_INTERACTIVE(true),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index 4aaa7ca..ca762fc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -18,6 +18,7 @@
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD;
+import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR;
import static com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl.NotificationInterruptEvent.HUN_SNOOZE_BYPASSED_POTENTIALLY_SUPPRESSED_FSI;
@@ -82,6 +83,9 @@
@UiEvent(doc = "FSI suppressed for suppressive GroupAlertBehavior")
FSI_SUPPRESSED_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR(1235),
+ @UiEvent(doc = "FSI suppressed for suppressive BubbleMetadata")
+ FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA(1353),
+
@UiEvent(doc = "FSI suppressed for requiring neither HUN nor keyguard")
FSI_SUPPRESSED_NO_HUN_OR_KEYGUARD(1236),
@@ -273,6 +277,16 @@
suppressedByDND);
}
+ // If the notification has suppressive BubbleMetadata, block FSI and warn.
+ Notification.BubbleMetadata bubbleMetadata = sbn.getNotification().getBubbleMetadata();
+ if (bubbleMetadata != null && bubbleMetadata.isNotificationSuppressed()) {
+ // b/274759612: Detect and report an event when a notification has both an FSI and a
+ // suppressive BubbleMetadata, and now correctly block the FSI from firing.
+ return getDecisionGivenSuppression(
+ FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
+ suppressedByDND);
+ }
+
// Notification is coming from a suspended package, block FSI
if (entry.getRanking().isSuspended()) {
return getDecisionGivenSuppression(FullScreenIntentDecision.NO_FSI_SUSPENDED,
@@ -351,6 +365,14 @@
mLogger.logNoFullscreenWarning(entry,
decision + ": GroupAlertBehavior will prevent HUN");
return;
+ case NO_FSI_SUPPRESSIVE_BUBBLE_METADATA:
+ android.util.EventLog.writeEvent(0x534e4554, "274759612", uid,
+ "bubbleMetadata");
+ mUiEventLogger.log(FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA, uid,
+ packageName);
+ mLogger.logNoFullscreenWarning(entry,
+ decision + ": BubbleMetadata may prevent HUN");
+ return;
case NO_FSI_NO_HUN_OR_KEYGUARD:
android.util.EventLog.writeEvent(0x534e4554, "231322873", uid,
"no hun or keyguard");
@@ -482,6 +504,12 @@
return false;
}
+ if (entry.getRanking().getLockscreenVisibilityOverride()
+ == Notification.VISIBILITY_PRIVATE) {
+ if (log) mLogger.logNoPulsingNotificationHidden(entry);
+ return false;
+ }
+
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
if (log) mLogger.logNoPulsingNotImportant(entry);
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index 797038d..ce6dd89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -110,7 +110,7 @@
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
- mTitleView.setText(title.toString());
+ mTitleView.setText(title != null ? title.toString() : title);
mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
if (TextUtils.isEmpty(text)) {
mTextView.setVisibility(GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index adbfa75..5f4c926 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -290,6 +290,9 @@
int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification
: com.android.internal.R.drawable.ic_expand_notification;
mExpandButton.setImageResource(drawableId);
+ mExpandButton.setContentDescription(mContext.getString(show
+ ? com.android.internal.R.string.expand_button_content_description_expanded
+ : com.android.internal.R.string.expand_button_content_description_collapsed));
if (mExpanded != show) {
mExpanded = show;
animateSnoozeOptions(show);
@@ -373,6 +376,7 @@
} else if (id == R.id.notification_snooze) {
// Toggle snooze options
showSnoozeOptions(!mExpanded);
+ mSnoozeView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
} else {
// Undo snooze was selected
@@ -401,6 +405,7 @@
public View getContentView() {
// Reset the view before use
setSelected(mDefaultOption, false);
+ showSnoozeOptions(false);
return this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 769edf7..1b49717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -56,7 +56,6 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
@@ -748,7 +747,6 @@
!mKeyguardBypassController.getBypassEnabled());
mSwipeHelper = mNotificationSwipeHelperBuilder
- .setSwipeDirection(SwipeHelper.X)
.setNotificationCallback(mNotificationCallback)
.setOnMenuEventListener(mMenuEventListener)
.build();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index b476b68..91f53b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -76,11 +76,10 @@
ViewConfiguration viewConfiguration,
FalsingManager falsingManager,
FeatureFlags featureFlags,
- int swipeDirection,
NotificationCallback callback,
NotificationMenuRowPlugin.OnMenuEventListener menuListener,
NotificationRoundnessManager notificationRoundnessManager) {
- super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+ super(callback, resources, viewConfiguration, falsingManager, featureFlags);
mNotificationRoundnessManager = notificationRoundnessManager;
mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
mMenuListener = menuListener;
@@ -416,22 +415,12 @@
}
@Override
- public boolean swipedFastEnough(float translation, float viewSize) {
- return swipedFastEnough();
- }
-
- @Override
@VisibleForTesting
protected boolean swipedFastEnough() {
return super.swipedFastEnough();
}
@Override
- public boolean swipedFarEnough(float translation, float viewSize) {
- return swipedFarEnough();
- }
-
- @Override
@VisibleForTesting
protected boolean swipedFarEnough() {
return super.swipedFarEnough();
@@ -554,7 +543,6 @@
private final ViewConfiguration mViewConfiguration;
private final FalsingManager mFalsingManager;
private final FeatureFlags mFeatureFlags;
- private int mSwipeDirection;
private NotificationCallback mNotificationCallback;
private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -570,11 +558,6 @@
mNotificationRoundnessManager = notificationRoundnessManager;
}
- Builder setSwipeDirection(int swipeDirection) {
- mSwipeDirection = swipeDirection;
- return this;
- }
-
Builder setNotificationCallback(NotificationCallback notificationCallback) {
mNotificationCallback = notificationCallback;
return this;
@@ -588,7 +571,7 @@
NotificationSwipeHelper build() {
return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
- mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+ mFeatureFlags, mNotificationCallback, mOnMenuEventListener,
mNotificationRoundnessManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f06b5db..f9493f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -281,7 +281,6 @@
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
- private boolean mIsModernAlternateBouncerEnabled;
private boolean mIsBackAnimationEnabled;
private final boolean mUdfpsNewTouchDetectionEnabled;
private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
@@ -363,7 +362,6 @@
mPrimaryBouncerView = primaryBouncerView;
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
- mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
mAlternateBouncerInteractor = alternateBouncerInteractor;
mIsBackAnimationEnabled =
featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM);
@@ -395,35 +393,6 @@
registerListeners();
}
- /**
- * Sets the given legacy alternate bouncer to null if it's the current alternate bouncer. Else,
- * does nothing. Only used if modern alternate bouncer is NOT enabled.
- */
- public void removeLegacyAlternateBouncer(
- @NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
- if (!mIsModernAlternateBouncerEnabled) {
- if (Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
- alternateBouncerLegacy)) {
- mAlternateBouncerInteractor.setLegacyAlternateBouncer(null);
- hideAlternateBouncer(true);
- }
- }
- }
-
- /**
- * Sets a new legacy alternate bouncer. Only used if modern alternate bouncer is NOT enabled.
- */
- public void setLegacyAlternateBouncer(@NonNull LegacyAlternateBouncer alternateBouncerLegacy) {
- if (!mIsModernAlternateBouncerEnabled) {
- if (!Objects.equals(mAlternateBouncerInteractor.getLegacyAlternateBouncer(),
- alternateBouncerLegacy)) {
- mAlternateBouncerInteractor.setLegacyAlternateBouncer(alternateBouncerLegacy);
- hideAlternateBouncer(true);
- }
- }
-
- }
-
/**
* Sets the given OccludingAppBiometricUI to null if it's the current auth interceptor. Else,
@@ -1386,7 +1355,6 @@
public void dump(PrintWriter pw) {
pw.println("StatusBarKeyguardViewManager:");
- pw.println(" mIsModernAlternateBouncerEnabled: " + mIsModernAlternateBouncerEnabled);
pw.println(" mRemoteInputActive: " + mRemoteInputActive);
pw.println(" mDozing: " + mDozing);
pw.println(" mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
@@ -1585,28 +1553,6 @@
}
/**
- * @deprecated Delegate used to send show and hide events to an alternate bouncer.
- */
- public interface LegacyAlternateBouncer {
- /**
- * Show alternate authentication bouncer.
- * @return whether alternate auth method was newly shown
- */
- boolean showAlternateBouncer();
-
- /**
- * Hide alternate authentication bouncer
- * @return whether the alternate auth method was newly hidden
- */
- boolean hideAlternateBouncer();
-
- /**
- * @return true if the alternate auth bouncer is showing
- */
- boolean isShowingAlternateBouncer();
- }
-
- /**
* Delegate used to send show and hide events to an alternate authentication method instead of
* the regular pin/pattern/password bouncer.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
index 32c64f4..8c61ada 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt
@@ -36,6 +36,7 @@
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.wrapper.BuildInfo
import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@
private val globalSettings: GlobalSettings,
private val userTracker: UserTracker,
private val dumpManager: DumpManager,
+ private val buildInfo: BuildInfo,
@Background private val backgroundHandler: Handler,
@Main private val mainExecutor: Executor
) : DeviceProvisionedController,
@@ -187,7 +189,7 @@
}
override fun isFrpActive(): Boolean {
- return frpActive.get()
+ return frpActive.get() && !buildInfo.isDebuggable
}
override fun isUserSetup(user: Int): Boolean {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 50645e5..7ce2b1c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -151,10 +152,19 @@
false);
}
-
@Test
public void testReset() {
mKeyguardAbsKeyInputViewController.reset();
verify(mKeyguardMessageAreaController).setMessage("", false);
+ verify(mAbsKeyInputView).resetPasswordText(false, false);
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
+ }
+
+ @Test
+ public void onResume_Reset() {
+ mKeyguardAbsKeyInputViewController.onResume(KeyguardSecurityView.VIEW_REVEALED);
+ verify(mKeyguardMessageAreaController).setMessage("", false);
+ verify(mAbsKeyInputView).resetPasswordText(false, false);
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 85dbdb8..6ae28b7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -31,6 +31,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.never
@@ -119,4 +120,24 @@
mKeyguardPatternViewController.startAppearAnimation()
verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
}
+
+ @Test
+ fun reset() {
+ mKeyguardPatternViewController.reset()
+ verify(mLockPatternView).setInStealthMode(anyBoolean())
+ verify(mLockPatternView).enableInput()
+ verify(mLockPatternView).setEnabled(true)
+ verify(mLockPatternView).clearPattern()
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
+
+ @Test
+ fun resume() {
+ mKeyguardPatternViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+ verify(mLockPatternView).setInStealthMode(anyBoolean())
+ verify(mLockPatternView).enableInput()
+ verify(mLockPatternView).setEnabled(true)
+ verify(mLockPatternView).clearPattern()
+ verify(mLockPatternUtils).getLockoutAttemptDeadline(anyInt())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index a1af8e8..70476aa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -129,10 +129,11 @@
}
@Test
- fun startAppearAnimation_withAutoPinConfirmation() {
+ fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
`when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+ `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
`when`(passwordTextView.text).thenReturn("")
pinViewController.startAppearAnimation()
@@ -141,4 +142,19 @@
verify(passwordTextView).setUsePinShapes(true)
verify(passwordTextView).setIsPinHinting(true)
}
+
+ @Test
+ fun startAppearAnimation_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
+ `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
+ `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+ `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+ `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
+ `when`(passwordTextView.text).thenReturn("")
+
+ pinViewController.startAppearAnimation()
+ verify(deleteButton).visibility = View.INVISIBLE
+ verify(enterButton).visibility = View.VISIBLE
+ verify(passwordTextView).setUsePinShapes(true)
+ verify(passwordTextView).setIsPinHinting(true)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index afb54d2..eaf7b1e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FeatureFlags;
@@ -42,6 +43,7 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -76,6 +78,8 @@
private KeyguardSecurityCallback mKeyguardSecurityCallback;
@Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private ViewMediatorCallback mViewMediatorCallback;
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@@ -92,7 +96,7 @@
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory,
- mEmergencyButtonControllerFactory, mFeatureFlags);
+ mEmergencyButtonControllerFactory, mFeatureFlags, mViewMediatorCallback);
}
@Test
@@ -123,6 +127,19 @@
}
@Test
+ public void asynchronouslyInflateView_setNeedsInput() {
+ ArgumentCaptor<AsyncLayoutInflater.OnInflateFinishedListener> argumentCaptor =
+ ArgumentCaptor.forClass(AsyncLayoutInflater.OnInflateFinishedListener.class);
+ mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN,
+ mKeyguardSecurityCallback, null);
+ verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), argumentCaptor.capture());
+ argumentCaptor.getValue().onInflateFinished(
+ LayoutInflater.from(getContext()).inflate(R.layout.keyguard_password_view, null),
+ R.layout.keyguard_password_view, mView);
+ verify(mViewMediatorCallback).setNeedsInput(anyBoolean());
+ }
+
+ @Test
public void onDensityOrFontScaleChanged() {
mKeyguardSecurityViewFlipperController.clearViews();
verify(mView).removeAllViews();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index a8b4254..4ed4e20 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1360,9 +1360,7 @@
public void startsListeningForSfps_whenKeyguardIsVisible_ifRequireInteractiveToAuthEnabled()
throws RemoteException {
// SFPS supported and enrolled
- final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
- when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsSupported()).thenReturn(true);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
// WHEN require interactive to auth is disabled, and keyguard is not awake
@@ -1401,9 +1399,7 @@
public void notListeningForSfps_whenGoingToSleep_ifRequireInteractiveToAuthEnabled()
throws RemoteException {
// GIVEN SFPS supported and enrolled
- final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
- when(mAuthController.getSfpsProps()).thenReturn(props);
+ when(mAuthController.isSfpsSupported()).thenReturn(true);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
// GIVEN Preconditions for sfps auth to run
@@ -2843,8 +2839,7 @@
}
private void givenUdfpsSupported() {
- Assert.assertFalse(mFingerprintSensorProperties.isEmpty());
- when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties);
+ when(mAuthController.isUdfpsSupported()).thenReturn(true);
Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 0ab675c..33345b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -51,13 +51,10 @@
import android.view.WindowMetrics
import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
@@ -113,7 +110,6 @@
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
- private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
private lateinit var overlayController: ISidefpsController
private lateinit var sideFpsController: SideFpsController
@@ -134,7 +130,6 @@
@Before
fun setup() {
- featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
@@ -144,8 +139,6 @@
FakeBiometricSettingsRepository(),
FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
- mock(KeyguardUpdateMonitor::class.java),
- featureFlags,
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
@@ -246,7 +239,6 @@
handler,
alternateBouncerInteractor,
TestCoroutineScope(),
- featureFlags,
dumpManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
index dbbc266..a878aec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -147,7 +147,6 @@
protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
boolean useModernBouncer, boolean useExpandedOverlay) {
- mFeatureFlags.set(Flags.MODERN_ALTERNATE_BOUNCER, useModernBouncer);
mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, useExpandedOverlay);
UdfpsKeyguardViewController controller = new UdfpsKeyguardViewController(
mView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
index cefa9b1..b848413 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -21,9 +21,7 @@
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
@@ -100,8 +98,6 @@
mock(BiometricSettingsRepository::class.java),
mock(DeviceEntryFingerprintAuthRepository::class.java),
mock(SystemClock::class.java),
- mock(KeyguardUpdateMonitor::class.java),
- mock(FeatureFlags::class.java)
)
return createUdfpsKeyguardViewController(
/* useModernBouncer */ true, /* useExpandedOverlay */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 0519a44..70f766f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -21,6 +21,7 @@
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.mockito.whenever
@@ -37,6 +38,7 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -46,7 +48,9 @@
class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dumpManager: DumpManager
- @Captor private lateinit var callbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+ @Mock private lateinit var authController: AuthController
+ @Captor
+ private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private lateinit var testScope: TestScope
@@ -59,6 +63,7 @@
underTest =
DeviceEntryFingerprintAuthRepositoryImpl(
+ authController,
keyguardUpdateMonitor,
testScope.backgroundScope,
dumpManager,
@@ -67,7 +72,7 @@
@After
fun tearDown() {
- verify(keyguardUpdateMonitor).removeCallback(callbackCaptor.value)
+ // verify(keyguardUpdateMonitor).removeCallback(updateMonitorCallback.value)
}
@Test
@@ -76,8 +81,8 @@
val isLockedOutValue = collectLastValue(underTest.isLockedOut)
runCurrent()
- verify(keyguardUpdateMonitor).registerCallback(callbackCaptor.capture())
- val callback = callbackCaptor.value
+ verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+ val callback = updateMonitorCallback.value
whenever(keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(true)
callback.onLockedOutStateChanged(BiometricSourceType.FACE)
@@ -90,4 +95,63 @@
callback.onLockedOutStateChanged(BiometricSourceType.FINGERPRINT)
assertThat(isLockedOutValue()).isFalse()
}
+
+ @Test
+ fun fpRunningStateIsPropagated() =
+ testScope.runTest {
+ val isRunning = collectLastValue(underTest.isRunning)
+ whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true)
+
+ // Initial value is available
+ assertThat(isRunning()).isTrue()
+
+ verify(keyguardUpdateMonitor, atLeastOnce())
+ .registerCallback(updateMonitorCallback.capture())
+ invokeOnCallback {
+ it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT)
+ }
+
+ assertThat(isRunning()).isFalse()
+
+ invokeOnCallback { it.onBiometricRunningStateChanged(true, BiometricSourceType.FACE) }
+
+ assertThat(isRunning()).isFalse()
+
+ updateMonitorCallback.value.onBiometricRunningStateChanged(
+ true,
+ BiometricSourceType.FINGERPRINT
+ )
+ assertThat(isRunning()).isTrue()
+ }
+
+ private fun invokeOnCallback(action: (KeyguardUpdateMonitorCallback) -> Unit) {
+ updateMonitorCallback.allValues.forEach { action(it) }
+ }
+
+ @Test
+ fun enabledFingerprintTypeProvidesTheCorrectOutput() =
+ testScope.runTest {
+ whenever(authController.isSfpsSupported).thenReturn(true)
+ whenever(authController.isUdfpsSupported).thenReturn(false)
+ whenever(authController.isRearFpsSupported).thenReturn(false)
+
+ assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.SIDE_FINGERPRINT)
+
+ whenever(authController.isSfpsSupported).thenReturn(false)
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ whenever(authController.isRearFpsSupported).thenReturn(false)
+
+ assertThat(underTest.availableFpSensorType)
+ .isEqualTo(BiometricType.UNDER_DISPLAY_FINGERPRINT)
+
+ whenever(authController.isSfpsSupported).thenReturn(false)
+ whenever(authController.isUdfpsSupported).thenReturn(false)
+ whenever(authController.isRearFpsSupported).thenReturn(true)
+
+ assertThat(underTest.availableFpSensorType).isEqualTo(BiometricType.REAR_FINGERPRINT)
+
+ whenever(authController.isRearFpsSupported).thenReturn(false)
+
+ assertThat(underTest.availableFpSensorType).isNull()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
index 86246f7..e7e5969 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -18,11 +18,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
@@ -40,10 +37,8 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.mock
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@@ -58,9 +53,7 @@
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var systemClock: SystemClock
- @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var bouncerLogger: TableLogBuffer
- private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setup() {
@@ -74,7 +67,6 @@
)
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
- featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) }
underTest =
AlternateBouncerInteractor(
statusBarStateController,
@@ -83,8 +75,6 @@
biometricSettingsRepository,
deviceEntryFingerprintAuthRepository,
systemClock,
- keyguardUpdateMonitor,
- featureFlags,
)
}
@@ -135,14 +125,6 @@
}
@Test
- fun canShowAlternateBouncerForFingerprint_isDozing() {
- givenCanShowAlternateBouncer()
- whenever(statusBarStateController.isDozing).thenReturn(true)
-
- assertFalse(underTest.canShowAlternateBouncerForFingerprint())
- }
-
- @Test
fun show_whenCanShow() {
givenCanShowAlternateBouncer()
@@ -182,42 +164,6 @@
assertFalse(bouncerRepository.alternateBouncerVisible.value)
}
- @Test
- fun onUnlockedIsFalse_doesNotHide() {
- // GIVEN alternate bouncer is showing
- bouncerRepository.setAlternateVisible(true)
-
- val keyguardStateControllerCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
- verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
-
- // WHEN isUnlocked=false
- givenCanShowAlternateBouncer()
- whenever(keyguardStateController.isUnlocked).thenReturn(false)
- keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
-
- // THEN the alternate bouncer is still visible
- assertTrue(bouncerRepository.alternateBouncerVisible.value)
- }
-
- @Test
- fun onUnlockedChangedIsTrue_hide() {
- // GIVEN alternate bouncer is showing
- bouncerRepository.setAlternateVisible(true)
-
- val keyguardStateControllerCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java)
- verify(keyguardStateController).addCallback(keyguardStateControllerCallbackCaptor.capture())
-
- // WHEN isUnlocked=true
- givenCanShowAlternateBouncer()
- whenever(keyguardStateController.isUnlocked).thenReturn(true)
- keyguardStateControllerCallbackCaptor.value.onUnlockedChanged()
-
- // THEN the alternate bouncer is hidden
- assertFalse(bouncerRepository.alternateBouncerVisible.value)
- }
-
private fun givenCanShowAlternateBouncer() {
bouncerRepository.setAlternateBouncerUIAvailable(true)
biometricSettingsRepository.setFingerprintEnrolled(true)
@@ -225,7 +171,6 @@
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
deviceEntryFingerprintAuthRepository.setLockedOut(false)
whenever(keyguardStateController.isUnlocked).thenReturn(false)
- whenever(statusBarStateController.isDozing).thenReturn(false)
}
private fun givenCannotShowAlternateBouncer() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
new file mode 100644
index 0000000..891a6f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.KeyguardManager;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.media.AudioManager;
+import android.media.session.MediaSessionManager;
+import android.os.PowerExemptionManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
+import com.android.settingslib.media.BluetoothMediaDevice;
+import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
+
+ private static final String TEST_PACKAGE = "test_package";
+
+ // Mock
+ private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
+ private final LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
+ private final LocalBluetoothProfileManager mLocalBluetoothProfileManager = mock(
+ LocalBluetoothProfileManager.class);
+ private final LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast = mock(
+ LocalBluetoothLeBroadcast.class);
+ private final LocalBluetoothLeBroadcastAssistant mLocalBluetoothLeBroadcastAssistant = mock(
+ LocalBluetoothLeBroadcastAssistant.class);
+ private final BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata = mock(
+ BluetoothLeBroadcastMetadata.class);
+ private final BluetoothLeBroadcastReceiveState mBluetoothLeBroadcastReceiveState = mock(
+ BluetoothLeBroadcastReceiveState.class);
+ private final ActivityStarter mStarter = mock(ActivityStarter.class);
+ private final BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
+ private final LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
+ private final MediaDevice mBluetoothMediaDevice = mock(BluetoothMediaDevice.class);
+ private final BluetoothDevice mBluetoothDevice = mock(BluetoothDevice.class);
+ private final CachedBluetoothDevice mCachedBluetoothDevice = mock(CachedBluetoothDevice.class);
+ private final CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
+ private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+ private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
+ NearbyMediaDevicesManager.class);
+ private final AudioManager mAudioManager = mock(AudioManager.class);
+ private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
+ private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+ private FeatureFlags mFlags = mock(FeatureFlags.class);
+
+ private MediaOutputBroadcastDialog mMediaOutputBroadcastDialog;
+ private MediaOutputController mMediaOutputController;
+
+ @Before
+ public void setUp() {
+ when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
+
+ mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
+ mMediaSessionManager, mLocalBluetoothManager, mStarter,
+ mNotifCollection, mDialogLaunchAnimator,
+ Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
+ mKeyguardManager, mFlags);
+ mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
+ mMediaOutputBroadcastDialog = new MediaOutputBroadcastDialog(mContext, false,
+ mBroadcastSender, mMediaOutputController);
+ mMediaOutputBroadcastDialog.show();
+ }
+
+ @After
+ public void tearDown() {
+ mMediaOutputBroadcastDialog.dismissDialog();
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noBroadcastMetadata_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(null);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noConnectedMediaDevice_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(null);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_hasBroadcastSource_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+ when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice())
+ .thenReturn(mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ sourceList.add(mBluetoothLeBroadcastReceiveState);
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ sourceList);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, never()).addSource(any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void connectBroadcastWithActiveDevice_noBroadcastSource_failToAddSource() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(
+ mLocalBluetoothLeBroadcastAssistant);
+ when(mLocalBluetoothLeBroadcast.getLatestBluetoothLeBroadcastMetadata()).thenReturn(
+ mBluetoothLeBroadcastMetadata);
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mBluetoothMediaDevice);
+ when(mBluetoothMediaDevice.isBLEDevice()).thenReturn(true);
+ when(((BluetoothMediaDevice) mBluetoothMediaDevice).getCachedDevice()).thenReturn(
+ mCachedBluetoothDevice);
+ when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+ List<BluetoothLeBroadcastReceiveState> sourceList = new ArrayList<>();
+ when(mLocalBluetoothLeBroadcastAssistant.getAllSources(mBluetoothDevice)).thenReturn(
+ sourceList);
+
+ mMediaOutputBroadcastDialog.connectBroadcastWithActiveDevice();
+
+ verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 09b00e2..ae6ced4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -19,12 +19,14 @@
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
+import static android.app.Notification.VISIBILITY_PRIVATE;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static android.app.NotificationManager.VISIBILITY_NO_OVERRIDE;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -222,10 +224,26 @@
ensureStateForHeadsUpWhenDozing();
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ modifyRanking(entry)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE)
+ .build();
+
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isTrue();
}
@Test
+ public void testShouldHeadsUpWhenDozing_hiddenOnLockscreen() {
+ ensureStateForHeadsUpWhenDozing();
+
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ modifyRanking(entry)
+ .setVisibilityOverride(VISIBILITY_PRIVATE)
+ .build();
+
+ assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
+ }
+
+ @Test
public void testShouldNotHeadsUpWhenDozing_pulseDisabled() {
// GIVEN state for "heads up when dozing" is true
ensureStateForHeadsUpWhenDozing();
@@ -638,6 +656,39 @@
}
@Test
+ public void testShouldNotFullScreen_isSuppressedByBubbleMetadata_withStrictFlag() {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_isSuppressedByBubbleMetadata();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_isSuppressedByBubbleMetadata() {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+ .setSuppressNotification(true).build();
+ entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mStatusBarStateController.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.getFullScreenIntentDecision(entry))
+ .isEqualTo(FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA);
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry,
+ "NO_FSI_SUPPRESSIVE_BUBBLE_METADATA: BubbleMetadata may prevent HUN");
+ verify(mLogger, never()).logFullscreen(any(), any());
+
+ assertThat(mUiEventLoggerFake.numLogs()).isEqualTo(1);
+ UiEventLoggerFake.FakeUiEvent fakeUiEvent = mUiEventLoggerFake.get(0);
+ assertThat(fakeUiEvent.eventId).isEqualTo(
+ NotificationInterruptEvent.FSI_SUPPRESSED_SUPPRESSIVE_BUBBLE_METADATA.getId());
+ assertThat(fakeUiEvent.uid).isEqualTo(entry.getSbn().getUid());
+ assertThat(fakeUiEvent.packageName).isEqualTo(entry.getSbn().getPackageName());
+ }
+
+ @Test
public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
testShouldFullScreen_notInteractive();
@@ -646,6 +697,9 @@
@Test
public void testShouldFullScreen_notInteractive() throws RemoteException {
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder("foo")
+ .setSuppressNotification(false).build();
+ entry.getSbn().getNotification().setBubbleMetadata(bubbleMetadata);
when(mPowerManager.isInteractive()).thenReturn(false);
when(mStatusBarStateController.isDreaming()).thenReturn(false);
when(mStatusBarStateController.getState()).thenReturn(SHADE);
@@ -879,6 +933,7 @@
NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
Set<FullScreenIntentDecision> warnings = new HashSet<>(Arrays.asList(
FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_GROUP_ALERT_BEHAVIOR,
+ FullScreenIntentDecision.NO_FSI_SUPPRESSIVE_BUBBLE_METADATA,
FullScreenIntentDecision.NO_FSI_NO_HUN_OR_KEYGUARD
));
for (FullScreenIntentDecision decision : FullScreenIntentDecision.values()) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 824eb4a..551499e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -43,7 +43,6 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.SwipeHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.flags.FeatureFlags;
@@ -101,7 +100,6 @@
ViewConfiguration.get(mContext),
new FalsingManagerFake(),
mFeatureFlags,
- SwipeHelper.X,
mCallback,
mListener,
mNotificationRoundnessManager));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
index 6980a0b..6094135 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt
@@ -27,8 +27,10 @@
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.wrapper.BuildInfo
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -36,9 +38,9 @@
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -58,6 +60,8 @@
private lateinit var dumpManager: DumpManager
@Mock
private lateinit var listener: DeviceProvisionedController.DeviceProvisionedListener
+ @Mock
+ private lateinit var buildInfo: BuildInfo
@Captor
private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
@@ -72,12 +76,13 @@
mainExecutor = FakeExecutor(FakeSystemClock())
settings = FakeSettings()
`when`(userTracker.userId).thenReturn(START_USER)
-
+ whenever(buildInfo.isDebuggable).thenReturn(false)
controller = DeviceProvisionedControllerImpl(
settings,
settings,
userTracker,
dumpManager,
+ buildInfo,
Handler(testableLooper.looper),
mainExecutor
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
index e1ba074..ce8d93e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -69,10 +69,10 @@
flow: Flow<T>,
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
-): FlowValues<T> {
+): FlowValue<List<T>> {
val values = mutableListOf<T>()
backgroundScope.launch(context, start) { flow.collect(values::add) }
- return FlowValuesImpl {
+ return FlowValueImpl {
runCurrent()
values.toList()
}
@@ -83,17 +83,7 @@
operator fun invoke(): T
}
-/** @see collectValues */
-interface FlowValues<T> : ReadOnlyProperty<Any?, List<T>> {
- operator fun invoke(): List<T>
-}
-
private class FlowValueImpl<T>(private val block: () -> T) : FlowValue<T> {
override operator fun invoke(): T = block()
override fun getValue(thisRef: Any?, property: KProperty<*>): T = invoke()
}
-
-private class FlowValuesImpl<T>(private val block: () -> List<T>) : FlowValues<T> {
- override operator fun invoke(): List<T> = block()
- override fun getValue(thisRef: Any?, property: KProperty<*>): List<T> = invoke()
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index 5641832..00b1a40 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -17,14 +17,22 @@
package com.android.systemui.keyguard.data.repository
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository {
- private val _isLockedOut = MutableStateFlow<Boolean>(false)
+ private val _isLockedOut = MutableStateFlow(false)
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
+ private val _isRunning = MutableStateFlow(false)
+ override val isRunning: Flow<Boolean>
+ get() = _isRunning
+
+ override val availableFpSensorType: BiometricType?
+ get() = null
+
fun setLockedOut(lockedOut: Boolean) {
_isLockedOut.value = lockedOut
}
diff --git a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
index fa30a6f..e605514 100644
--- a/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
+++ b/services/accessibility/java/com/android/server/accessibility/FlashNotificationsController.java
@@ -123,11 +123,9 @@
private static final int SCREEN_DEFAULT_COLOR_WITH_ALPHA =
SCREEN_DEFAULT_COLOR | SCREEN_DEFAULT_ALPHA;
- // TODO(b/266775677): Make protected-broadcast intent
@VisibleForTesting
static final String ACTION_FLASH_NOTIFICATION_START_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW";
- // TODO(b/266775677): Make protected-broadcast intent
@VisibleForTesting
static final String ACTION_FLASH_NOTIFICATION_STOP_PREVIEW =
"com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW";
@@ -143,13 +141,10 @@
@VisibleForTesting
static final int PREVIEW_TYPE_LONG = 1;
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_CAMERA_FLASH_NOTIFICATION = "camera_flash_notification";
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION = "screen_flash_notification";
- // TODO(b/266775683): Move to settings provider
@VisibleForTesting
static final String SETTING_KEY_SCREEN_FLASH_NOTIFICATION_COLOR =
"screen_flash_notification_color_global";
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 7f24c52..3d3535d 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -25,7 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
-import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
import com.android.server.utils.TimingsTraceAndSlog;
import java.io.PrintWriter;
@@ -188,12 +188,12 @@
}
/**
- * A helper function to call ActivityManagerService.dumpStackTraces().
+ * A helper function to call StackTracesDumpHelper.dumpStackTraces().
*/
private static void dumpStackTraces() {
final ArrayList<Integer> pids = new ArrayList<>();
pids.add(Process.myPid());
- ActivityManagerService.dumpStackTraces(pids,
+ StackTracesDumpHelper.dumpStackTraces(pids,
/* processCpuTracker= */null, /* lastPids= */null,
CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
/* logExceptionCreatingFile= */null, /* subject= */null,
diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java
index f652cb0..78d4708 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -1104,7 +1104,7 @@
final NetworkCapabilities result = ncBuilder.build();
final VcnUnderlyingNetworkPolicy policy = new VcnUnderlyingNetworkPolicy(
mTrackingNetworkCallback
- .requiresRestartForImmutableCapabilityChanges(result),
+ .requiresRestartForImmutableCapabilityChanges(result, linkProperties),
result);
logVdbg("getUnderlyingNetworkPolicy() called for caps: " + networkCapabilities
@@ -1354,19 +1354,29 @@
* without requiring a Network restart.
*/
private class TrackingNetworkCallback extends ConnectivityManager.NetworkCallback {
+ private final Object mLockObject = new Object();
private final Map<Network, NetworkCapabilities> mCaps = new ArrayMap<>();
+ private final Map<Network, LinkProperties> mLinkProperties = new ArrayMap<>();
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities caps) {
- synchronized (mCaps) {
+ synchronized (mLockObject) {
mCaps.put(network, caps);
}
}
@Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties lp) {
+ synchronized (mLockObject) {
+ mLinkProperties.put(network, lp);
+ }
+ }
+
+ @Override
public void onLost(Network network) {
- synchronized (mCaps) {
+ synchronized (mLockObject) {
mCaps.remove(network);
+ mLinkProperties.remove(network);
}
}
@@ -1393,22 +1403,28 @@
return true;
}
- private boolean requiresRestartForImmutableCapabilityChanges(NetworkCapabilities caps) {
+ private boolean requiresRestartForImmutableCapabilityChanges(
+ NetworkCapabilities caps, LinkProperties lp) {
if (caps.getSubscriptionIds() == null) {
return false;
}
- synchronized (mCaps) {
- for (NetworkCapabilities existing : mCaps.values()) {
- if (caps.getSubscriptionIds().equals(existing.getSubscriptionIds())
- && hasSameTransportsAndCapabilities(caps, existing)) {
- // Restart if any immutable capabilities have changed
- return existing.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ synchronized (mLockObject) {
+ // Search for an existing network (using interfce names)
+ // TODO: Get network from NetworkFactory (if exists) for this match.
+ for (Entry<Network, LinkProperties> lpEntry : mLinkProperties.entrySet()) {
+ if (lp.getInterfaceName() != null
+ && !lp.getInterfaceName().isEmpty()
+ && Objects.equals(
+ lp.getInterfaceName(), lpEntry.getValue().getInterfaceName())) {
+ return mCaps.get(lpEntry.getKey())
+ .hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
!= caps.hasCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
}
+ // If no network found, by definition does not need restart.
return false;
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 03821db..62651dd 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -56,6 +56,7 @@
import com.android.internal.os.ZygoteConnectionConstants;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
import com.android.server.am.TraceErrorLogger;
import com.android.server.criticalevents.CriticalEventLog;
import com.android.server.wm.SurfaceAnimationThread;
@@ -905,7 +906,7 @@
report.append(ResourcePressureUtil.currentPsiState());
ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
StringWriter tracesFileException = new StringWriter();
- final File stack = ActivityManagerService.dumpStackTraces(
+ final File stack = StackTracesDumpHelper.dumpStackTraces(
pids, processCpuTracker, new SparseBooleanArray(),
CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 461103e..8fe61e7 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -73,6 +73,7 @@
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
+import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
import static android.os.PowerExemptionManager.reasonCodeToString;
@@ -7319,9 +7320,10 @@
r.mAllowWhileInUsePermissionInFgs = true;
}
+ final @ReasonCode int allowWhileInUse;
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
- final @ReasonCode int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+ allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
isBindService);
if (!r.mAllowWhileInUsePermissionInFgs) {
@@ -7332,6 +7334,24 @@
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
+ } else {
+ allowWhileInUse = REASON_UNKNOWN;
+ }
+ // We want to allow scheduling user-initiated jobs when the app is running a
+ // foreground service that was started in the same conditions that allows for scheduling
+ // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
+ // an FGS that started when the app was in the TOP or a BAL-approved state.
+ // As of Android UDC, the conditions required for the while-in-use permissions
+ // are the same conditions that we want, so we piggyback on that logic.
+ // We use that as a shortcut if possible so we don't have to recheck all the conditions.
+ final boolean isFgs = r.isForeground || r.fgRequired;
+ if (isFgs) {
+ r.updateAllowUiJobScheduling(ActivityManagerService
+ .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse)
+ || mAm.canScheduleUserInitiatedJobs(
+ callingUid, callingPid, callingPackage, true));
+ } else {
+ r.updateAllowUiJobScheduling(false);
}
}
@@ -7342,6 +7362,7 @@
r.mInfoTempFgsAllowListReason = null;
r.mLoggedInfoAllowStartForeground = false;
r.mLastSetFgsRestrictionTime = 0;
+ r.updateAllowUiJobScheduling(r.mAllowWhileInUsePermissionInFgs);
}
boolean canStartForegroundServiceLocked(int callingPid, int callingUid, String callingPackage) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 65c4d75..713b993 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -114,7 +114,6 @@
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
import static android.view.Display.INVALID_DISPLAY;
@@ -123,7 +122,6 @@
import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NEW_MUTABLE_IMPLICIT_PENDING_INTENT_RETRIEVED;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALLOWLISTS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKGROUND_CHECK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
@@ -372,7 +370,6 @@
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -409,7 +406,6 @@
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
-import com.android.internal.os.anr.AnrLatencyTracker;
import com.android.internal.policy.AttributeCache;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
@@ -488,14 +484,10 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.charset.StandardCharsets;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -506,18 +498,13 @@
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
-import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BiFunction;
import java.util.function.Consumer;
-import java.util.function.Supplier;
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -555,8 +542,6 @@
static final String SYSTEM_USER_HOME_NEEDED = "ro.system_user_home_needed";
- public static final String ANR_TRACE_DIR = "/data/anr";
-
// Maximum number of receivers an app can register.
private static final int MAX_RECEIVERS_ALLOWED_PER_APP = 1000;
@@ -618,10 +603,6 @@
private static final int MAX_BUGREPORT_TITLE_SIZE = 100;
private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150;
- private static final int NATIVE_DUMP_TIMEOUT_MS =
- 2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds;
- private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes.
-
OomAdjuster mOomAdjuster;
static final String EXTRA_TITLE = "android.intent.extra.TITLE";
@@ -3428,432 +3409,6 @@
}
}
- /**
- * If a stack trace dump file is configured, dump process stack traces.
- * @param firstPids of dalvik VM processes to dump stack traces for first
- * @param lastPids of dalvik VM processes to dump stack traces for last
- * @param nativePids optional list of native pids to dump stack crawls
- * @param logExceptionCreatingFile optional writer to which we log errors creating the file
- * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
- * @param latencyTracker the latency tracker instance of the current ANR.
- */
- public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
- Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
- @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
- logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
- }
-
- /**
- * If a stack trace dump file is configured, dump process stack traces.
- * @param firstPids of dalvik VM processes to dump stack traces for first
- * @param lastPids of dalvik VM processes to dump stack traces for last
- * @param nativePids optional list of native pids to dump stack crawls
- * @param logExceptionCreatingFile optional writer to which we log errors creating the file
- * @param subject optional line related to the error
- * @param criticalEventSection optional lines containing recent critical events.
- * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
- * @param latencyTracker the latency tracker instance of the current ANR.
- */
- public static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
- Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
- String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
- AnrLatencyTracker latencyTracker) {
- return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
- logExceptionCreatingFile, null, subject, criticalEventSection,
- auxiliaryTaskExecutor, latencyTracker);
- }
-
- /**
- * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
- * of the very first pid to be dumped.
- */
- /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
- ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
- Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
- AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
- @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
- try {
-
- if (latencyTracker != null) {
- latencyTracker.dumpStackTracesStarted();
- }
-
- Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
-
- // Measure CPU usage as soon as we're called in order to get a realistic sampling
- // of the top users at the time of the request.
- Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
- ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
- Future<ArrayList<Integer>> extraPidsFuture = null;
- if (extraPidsSupplier != null) {
- extraPidsFuture =
- CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
- }
-
- final File tracesDir = new File(ANR_TRACE_DIR);
-
- // NOTE: We should consider creating the file in native code atomically once we've
- // gotten rid of the old scheme of dumping and lot of the code that deals with paths
- // can be removed.
- File tracesFile;
- try {
- tracesFile = createAnrDumpFile(tracesDir);
- } catch (IOException e) {
- Slog.w(TAG, "Exception creating ANR dump file:", e);
- if (logExceptionCreatingFile != null) {
- logExceptionCreatingFile.append(
- "----- Exception creating ANR dump file -----\n");
- e.printStackTrace(new PrintWriter(logExceptionCreatingFile));
- }
- if (latencyTracker != null) {
- latencyTracker.anrSkippedDumpStackTraces();
- }
- return null;
- }
-
- if (subject != null || criticalEventSection != null) {
- appendtoANRFile(tracesFile.getAbsolutePath(),
- (subject != null ? "Subject: " + subject + "\n\n" : "")
- + (criticalEventSection != null ? criticalEventSection : ""));
- }
-
- long firstPidEndPos = dumpStackTraces(
- tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
- extraPidsFuture, latencyTracker);
- if (firstPidEndOffset != null) {
- firstPidEndOffset.set(firstPidEndPos);
- }
- // Each set of ANR traces is written to a separate file and dumpstate will process
- // all such files and add them to a captured bug report if they're recent enough.
- maybePruneOldTraces(tracesDir);
-
- return tracesFile;
- } finally {
- if (latencyTracker != null) {
- latencyTracker.dumpStackTracesEnded();
- }
- }
-
- }
-
- @GuardedBy("ActivityManagerService.class")
- private static SimpleDateFormat sAnrFileDateFormat;
- static final String ANR_FILE_PREFIX = "anr_";
-
- private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
- SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsCalled();
- }
- ArrayList<Integer> extraPids = new ArrayList<>();
- processCpuTracker.init();
- try {
- Thread.sleep(200);
- } catch (InterruptedException ignored) {
- }
-
- processCpuTracker.update();
-
- // We'll take the stack crawls of just the top apps using CPU.
- final int workingStatsNumber = processCpuTracker.countWorkingStats();
- for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
- ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
- if (lastPids.indexOfKey(stats.pid) >= 0) {
- if (DEBUG_ANR) {
- Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
- }
-
- extraPids.add(stats.pid);
- } else {
- Slog.i(TAG,
- "Skipping next CPU consuming process, not a java proc: "
- + stats.pid);
- }
- }
- if (latencyTracker != null) {
- latencyTracker.processCpuTrackerMethodsReturned();
- }
- return extraPids;
- }
-
- private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
- if (sAnrFileDateFormat == null) {
- sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
- }
-
- final String formattedDate = sAnrFileDateFormat.format(new Date());
- final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);
-
- if (anrFile.createNewFile()) {
- FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
- return anrFile;
- } else {
- throw new IOException("Unable to create ANR dump file: createNewFile failed");
- }
- }
-
- /**
- * Prune all trace files that are more than a day old.
- *
- * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a
- * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now
- * since it's the system_server that creates trace files for most ANRs.
- */
- private static void maybePruneOldTraces(File tracesDir) {
- final File[] files = tracesDir.listFiles();
- if (files == null) return;
-
- final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64);
- final long now = System.currentTimeMillis();
- try {
- Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
- for (int i = 0; i < files.length; ++i) {
- if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) {
- if (!files[i].delete()) {
- Slog.w(TAG, "Unable to prune stale trace file: " + files[i]);
- }
- }
- }
- } catch (IllegalArgumentException e) {
- // The modification times changed while we were sorting. Bail...
- // https://issuetracker.google.com/169836837
- Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e);
- }
- }
-
- /**
- * Dump java traces for process {@code pid} to the specified file. If java trace dumping
- * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
- * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
- * attempting to obtain native traces in the case of a failure. Returns the total time spent
- * capturing traces.
- */
- private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
- final long timeStart = SystemClock.elapsedRealtime();
- int headerSize = writeUptimeStartHeaderForPid(pid, fileName);
- boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
- (int) (timeoutMs / 1000));
- if (javaSuccess) {
- // Check that something is in the file, actually. Try-catch should not be necessary,
- // but better safe than sorry.
- try {
- long size = new File(fileName).length();
- if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) {
- Slog.w(TAG, "Successfully created Java ANR file is empty!");
- javaSuccess = false;
- }
- } catch (Exception e) {
- Slog.w(TAG, "Unable to get ANR file size", e);
- javaSuccess = false;
- }
- }
- if (!javaSuccess) {
- Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump.");
- if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
- (NATIVE_DUMP_TIMEOUT_MS / 1000))) {
- Slog.w(TAG, "Native stack dump failed!");
- }
- }
-
- return SystemClock.elapsedRealtime() - timeStart;
- }
-
- private static int appendtoANRFile(String fileName, String text) {
- try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
- byte[] header = text.getBytes(StandardCharsets.UTF_8);
- fos.write(header);
- return header.length;
- } catch (IOException e) {
- Slog.w(TAG, "Exception writing to ANR dump file:", e);
- return 0;
- }
- }
-
- /*
- * Writes a header containing the process id and the current system uptime.
- */
- private static int writeUptimeStartHeaderForPid(int pid, String fileName) {
- return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at "
- + SystemClock.uptimeMillis() + "\n");
- }
-
-
- /**
- * @return The end offset of the trace of the very first PID
- */
- public static long dumpStackTraces(String tracesFile,
- ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
- Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
-
- Slog.i(TAG, "Dumping to " + tracesFile);
-
- // We don't need any sort of inotify based monitoring when we're dumping traces via
- // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
- // control of all writes to the file in question.
-
- // We must complete all stack dumps within 20 seconds.
- long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
-
- // As applications are usually interested with the ANR stack traces, but we can't share with
- // them the stack traces other than their own stacks. So after the very first PID is
- // dumped, remember the current file size.
- long firstPidEnd = -1;
-
- // First collect all of the stacks of the most important pids.
- if (firstPids != null) {
- if (latencyTracker != null) {
- latencyTracker.dumpingFirstPidsStarted();
- }
-
- int num = firstPids.size();
- for (int i = 0; i < num; i++) {
- final int pid = firstPids.get(i);
- // We don't copy ANR traces from the system_server intentionally.
- final boolean firstPid = i == 0 && MY_PID != pid;
- if (latencyTracker != null) {
- latencyTracker.dumpingPidStarted(pid);
- }
-
- Slog.i(TAG, "Collecting stacks for pid " + pid);
- final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
- remainingTime);
- if (latencyTracker != null) {
- latencyTracker.dumpingPidEnded();
- }
-
- remainingTime -= timeTaken;
- if (remainingTime <= 0) {
- Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
- + "); deadline exceeded.");
- return firstPidEnd;
- }
-
- if (firstPid) {
- firstPidEnd = new File(tracesFile).length();
- // Full latency dump
- if (latencyTracker != null) {
- appendtoANRFile(tracesFile,
- latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
- }
- }
- if (DEBUG_ANR) {
- Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
- }
- }
- if (latencyTracker != null) {
- latencyTracker.dumpingFirstPidsEnded();
- }
- }
-
- // Next collect the stacks of the native pids
- ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
-
- Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
-
- if (nativePids != null) {
- if (latencyTracker != null) {
- latencyTracker.dumpingNativePidsStarted();
- }
- for (int pid : nativePids) {
- Slog.i(TAG, "Collecting stacks for native pid " + pid);
- final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
-
- if (latencyTracker != null) {
- latencyTracker.dumpingPidStarted(pid);
- }
- final long start = SystemClock.elapsedRealtime();
- Debug.dumpNativeBacktraceToFileTimeout(
- pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
- final long timeTaken = SystemClock.elapsedRealtime() - start;
- if (latencyTracker != null) {
- latencyTracker.dumpingPidEnded();
- }
- remainingTime -= timeTaken;
- if (remainingTime <= 0) {
- Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid +
- "); deadline exceeded.");
- return firstPidEnd;
- }
-
- if (DEBUG_ANR) {
- Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms");
- }
- }
- if (latencyTracker != null) {
- latencyTracker.dumpingNativePidsEnded();
- }
- }
-
- // Lastly, dump stacks for all extra PIDs from the CPU tracker.
- ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
-
- if (extraPidsFuture != null) {
- try {
- extraPids = extraPidsFuture.get();
- } catch (ExecutionException e) {
- Slog.w(TAG, "Failed to collect extra pids", e.getCause());
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted while collecting extra pids", e);
- }
- }
- Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
-
- if (extraPids != null) {
- if (latencyTracker != null) {
- latencyTracker.dumpingExtraPidsStarted();
- }
- for (int pid : extraPids) {
- Slog.i(TAG, "Collecting stacks for extra pid " + pid);
- if (latencyTracker != null) {
- latencyTracker.dumpingPidStarted(pid);
- }
- final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
- if (latencyTracker != null) {
- latencyTracker.dumpingPidEnded();
- }
- remainingTime -= timeTaken;
- if (remainingTime <= 0) {
- Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
- "); deadline exceeded.");
- return firstPidEnd;
- }
-
- if (DEBUG_ANR) {
- Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
- }
- }
- if (latencyTracker != null) {
- latencyTracker.dumpingExtraPidsEnded();
- }
- }
- // Append the dumping footer with the current uptime
- appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
- Slog.i(TAG, "Done dumping");
-
- return firstPidEnd;
- }
-
- private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
- String logName) {
-
- ArrayList<Integer> pids = null;
-
- if (pidsFuture == null) {
- return pids;
- }
- try {
- pids = pidsFuture.get();
- } catch (ExecutionException e) {
- Slog.w(TAG, "Failed to collect " + logName, e.getCause());
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted while collecting " + logName , e);
- }
- return pids;
- }
-
@Override
public boolean clearApplicationUserData(final String packageName, boolean keepState,
final IPackageDataObserver observer, int userId) {
@@ -6563,7 +6118,7 @@
* This is a shortcut and <b>DOES NOT</b> include all reasons.
* Use {@link #canScheduleUserInitiatedJobs(int, int, String)} to cover all cases.
*/
- private boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
+ static boolean doesReasonCodeAllowSchedulingUserInitiatedJobs(int reasonCode) {
switch (reasonCode) {
case REASON_PROC_STATE_PERSISTENT:
case REASON_PROC_STATE_PERSISTENT_UI:
@@ -6621,6 +6176,16 @@
}
}
+ final ProcessServiceRecord psr = pr.mServices;
+ if (psr != null && psr.hasForegroundServices()) {
+ for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) {
+ final ServiceRecord sr = psr.getExecutingServiceAt(s);
+ if (sr.isForeground && sr.mAllowUiJobScheduling) {
+ return true;
+ }
+ }
+ }
+
return false;
}
@@ -6630,6 +6195,11 @@
*/
// TODO(262260570): log allow reason to an atom
private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
+ return canScheduleUserInitiatedJobs(uid, pid, pkgName, false);
+ }
+
+ boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName,
+ boolean skipWhileInUseCheck) {
synchronized (this) {
final ProcessRecord processRecord;
synchronized (mPidsSelfLocked) {
@@ -6659,7 +6229,7 @@
// As of Android UDC, the conditions required to grant a while-in-use permission
// covers the majority of those cases, and so we piggyback on that logic as the base.
// Missing cases are added after.
- if (mServices.canAllowWhileInUsePermissionInFgsLocked(
+ if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked(
pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
return true;
}
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 1ba3266..4443636 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -1153,7 +1153,7 @@
final ArraySet<String> allFiles = new ArraySet();
final File[] files = mProcExitStoreDir.listFiles((f) -> {
final String name = f.getName();
- boolean trace = name.startsWith(ActivityManagerService.ANR_FILE_PREFIX)
+ boolean trace = name.startsWith(StackTracesDumpHelper.ANR_FILE_PREFIX)
&& name.endsWith(APP_TRACE_FILE_SUFFIX);
if (trace) {
allFiles.add(name);
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2517c9c1..1d48cb2 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -493,7 +493,7 @@
StringWriter tracesFileException = new StringWriter();
// To hold the start and end offset to the ANR trace file respectively.
final AtomicLong firstPidEndOffset = new AtomicLong(-1);
- File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+ File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 35f71f7..4e401b2 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2574,7 +2574,10 @@
+ ", " + reason);
app.setPendingStart(false);
killProcessQuiet(pid);
- Process.killProcessGroup(app.uid, app.getPid());
+ final int appPid = app.getPid();
+ if (appPid != 0) {
+ Process.killProcessGroup(app.uid, appPid);
+ }
noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_INVALID_START, reason);
return false;
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 4defdc6..18ef66f 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -176,6 +176,8 @@
boolean mAllowWhileInUsePermissionInFgs;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
+ /** Allow scheduling user-initiated jobs from the background. */
+ boolean mAllowUiJobScheduling;
// the most recent package that start/bind this service.
String mRecentCallingPackage;
@@ -607,6 +609,7 @@
}
pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
pw.println(mAllowWhileInUsePermissionInFgs);
+ pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
pw.print(prefix); pw.print("recentCallingUid=");
@@ -1024,7 +1027,17 @@
ams.mConstants.SERVICE_BG_ACTIVITY_START_TIMEOUT);
}
+ void updateAllowUiJobScheduling(boolean allowUiJobScheduling) {
+ if (mAllowUiJobScheduling == allowUiJobScheduling) {
+ return;
+ }
+ mAllowUiJobScheduling = allowUiJobScheduling;
+ }
+
private void setAllowedBgActivityStartsByStart(BackgroundStartPrivileges newValue) {
+ if (mBackgroundStartPrivilegesByStartMerged == newValue) {
+ return;
+ }
mBackgroundStartPrivilegesByStartMerged = newValue;
updateParentProcessBgActivityStartsToken();
}
diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
new file mode 100644
index 0000000..9373328
--- /dev/null
+++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ANR;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.os.Build;
+import android.os.Debug;
+import android.os.FileUtils;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.anr.AnrLatencyTracker;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Supplier;
+
+
+/**
+ * A helper for dumping stack traces.
+ */
+public class StackTracesDumpHelper {
+ static final String TAG = TAG_WITH_CLASS_NAME ? "StackTracesDumpHelper" : TAG_AM;
+
+ @GuardedBy("StackTracesDumpHelper.class")
+ private static final SimpleDateFormat ANR_FILE_DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
+
+ static final String ANR_FILE_PREFIX = "anr_";
+ public static final String ANR_TRACE_DIR = "/data/anr";
+
+ private static final int NATIVE_DUMP_TIMEOUT_MS =
+ 2000 * Build.HW_TIMEOUT_MULTIPLIER; // 2 seconds;
+ private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes.
+
+ /**
+ * If a stack trace dump file is configured, dump process stack traces.
+ * @param firstPids of dalvik VM processes to dump stack traces for first
+ * @param lastPids of dalvik VM processes to dump stack traces for last
+ * @param nativePidsFuture optional future for a list of native pids to dump stack crawls
+ * @param logExceptionCreatingFile optional writer to which we log errors creating the file
+ * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
+ * @param latencyTracker the latency tracker instance of the current ANR.
+ */
+ public static File dumpStackTraces(ArrayList<Integer> firstPids,
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
+ }
+
+ /**
+ * @param subject the subject of the dumped traces
+ * @param criticalEventSection the critical event log, passed as a string
+ */
+ public static File dumpStackTraces(ArrayList<Integer> firstPids,
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+ AnrLatencyTracker latencyTracker) {
+ return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+ logExceptionCreatingFile, null, subject, criticalEventSection,
+ auxiliaryTaskExecutor, latencyTracker);
+ }
+
+ /**
+ * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
+ * of the very first pid to be dumped.
+ */
+ /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
+ ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+ Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+ AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
+ @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+ try {
+
+ if (latencyTracker != null) {
+ latencyTracker.dumpStackTracesStarted();
+ }
+
+ Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
+
+ // Measure CPU usage as soon as we're called in order to get a realistic sampling
+ // of the top users at the time of the request.
+ Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+ ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+ Future<ArrayList<Integer>> extraPidsFuture = null;
+ if (extraPidsSupplier != null) {
+ extraPidsFuture =
+ CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
+ }
+
+ final File tracesDir = new File(ANR_TRACE_DIR);
+
+ // NOTE: We should consider creating the file in native code atomically once we've
+ // gotten rid of the old scheme of dumping and lot of the code that deals with paths
+ // can be removed.
+ File tracesFile;
+ try {
+ tracesFile = createAnrDumpFile(tracesDir);
+ } catch (IOException e) {
+ Slog.w(TAG, "Exception creating ANR dump file:", e);
+ if (logExceptionCreatingFile != null) {
+ logExceptionCreatingFile.append(
+ "----- Exception creating ANR dump file -----\n");
+ e.printStackTrace(new PrintWriter(logExceptionCreatingFile));
+ }
+ if (latencyTracker != null) {
+ latencyTracker.anrSkippedDumpStackTraces();
+ }
+ return null;
+ }
+
+ if (subject != null || criticalEventSection != null) {
+ appendtoANRFile(tracesFile.getAbsolutePath(),
+ (subject != null ? "Subject: " + subject + "\n\n" : "")
+ + (criticalEventSection != null ? criticalEventSection : ""));
+ }
+
+ long firstPidEndPos = dumpStackTraces(
+ tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+ extraPidsFuture, latencyTracker);
+ if (firstPidEndOffset != null) {
+ firstPidEndOffset.set(firstPidEndPos);
+ }
+ // Each set of ANR traces is written to a separate file and dumpstate will process
+ // all such files and add them to a captured bug report if they're recent enough.
+ maybePruneOldTraces(tracesDir);
+
+ return tracesFile;
+ } finally {
+ if (latencyTracker != null) {
+ latencyTracker.dumpStackTracesEnded();
+ }
+ }
+
+ }
+
+ /**
+ * @return The end offset of the trace of the very first PID
+ */
+ public static long dumpStackTraces(String tracesFile,
+ ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+ Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
+
+ Slog.i(TAG, "Dumping to " + tracesFile);
+
+ // We don't need any sort of inotify based monitoring when we're dumping traces via
+ // tombstoned. Data is piped to an "intercept" FD installed in tombstoned so we're in full
+ // control of all writes to the file in question.
+
+ // We must complete all stack dumps within 20 seconds.
+ long remainingTime = 20 * 1000 * Build.HW_TIMEOUT_MULTIPLIER;
+
+ // As applications are usually interested with the ANR stack traces, but we can't share with
+ // them the stack traces other than their own stacks. So after the very first PID is
+ // dumped, remember the current file size.
+ long firstPidEnd = -1;
+
+ // First collect all of the stacks of the most important pids.
+ if (firstPids != null) {
+ if (latencyTracker != null) {
+ latencyTracker.dumpingFirstPidsStarted();
+ }
+
+ int num = firstPids.size();
+ for (int i = 0; i < num; i++) {
+ final int pid = firstPids.get(i);
+ // We don't copy ANR traces from the system_server intentionally.
+ final boolean firstPid = i == 0 && ActivityManagerService.MY_PID != pid;
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidStarted(pid);
+ }
+
+ Slog.i(TAG, "Collecting stacks for pid " + pid);
+ final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
+ remainingTime);
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidEnded();
+ }
+
+ remainingTime -= timeTaken;
+ if (remainingTime <= 0) {
+ Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
+ + "); deadline exceeded.");
+ return firstPidEnd;
+ }
+
+ if (firstPid) {
+ firstPidEnd = new File(tracesFile).length();
+ // Full latency dump
+ if (latencyTracker != null) {
+ appendtoANRFile(tracesFile,
+ latencyTracker.dumpAsCommaSeparatedArrayWithHeader());
+ }
+ }
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Done with pid " + firstPids.get(i) + " in " + timeTaken + "ms");
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.dumpingFirstPidsEnded();
+ }
+ }
+
+ // Next collect the stacks of the native pids
+ ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+ Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
+ if (nativePids != null) {
+ if (latencyTracker != null) {
+ latencyTracker.dumpingNativePidsStarted();
+ }
+ for (int pid : nativePids) {
+ Slog.i(TAG, "Collecting stacks for native pid " + pid);
+ final long nativeDumpTimeoutMs = Math.min(NATIVE_DUMP_TIMEOUT_MS, remainingTime);
+
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidStarted(pid);
+ }
+ final long start = SystemClock.elapsedRealtime();
+ Debug.dumpNativeBacktraceToFileTimeout(
+ pid, tracesFile, (int) (nativeDumpTimeoutMs / 1000));
+ final long timeTaken = SystemClock.elapsedRealtime() - start;
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidEnded();
+ }
+ remainingTime -= timeTaken;
+ if (remainingTime <= 0) {
+ Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid
+ + "); deadline exceeded.");
+ return firstPidEnd;
+ }
+
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Done with native pid " + pid + " in " + timeTaken + "ms");
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.dumpingNativePidsEnded();
+ }
+ }
+
+ // Lastly, dump stacks for all extra PIDs from the CPU tracker.
+ ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+ if (extraPidsFuture != null) {
+ try {
+ extraPids = extraPidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting extra pids", e);
+ }
+ }
+ Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
+ if (extraPids != null) {
+ if (latencyTracker != null) {
+ latencyTracker.dumpingExtraPidsStarted();
+ }
+ for (int pid : extraPids) {
+ Slog.i(TAG, "Collecting stacks for extra pid " + pid);
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidStarted(pid);
+ }
+ final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile, remainingTime);
+ if (latencyTracker != null) {
+ latencyTracker.dumpingPidEnded();
+ }
+ remainingTime -= timeTaken;
+ if (remainingTime <= 0) {
+ Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid
+ + "); deadline exceeded.");
+ return firstPidEnd;
+ }
+
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Done with extra pid " + pid + " in " + timeTaken + "ms");
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.dumpingExtraPidsEnded();
+ }
+ }
+ // Append the dumping footer with the current uptime
+ appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
+ Slog.i(TAG, "Done dumping");
+
+ return firstPidEnd;
+ }
+
+ private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
+ final String formattedDate = ANR_FILE_DATE_FORMAT.format(new Date());
+ final File anrFile = new File(tracesDir, ANR_FILE_PREFIX + formattedDate);
+
+ if (anrFile.createNewFile()) {
+ FileUtils.setPermissions(anrFile.getAbsolutePath(), 0600, -1, -1); // -rw-------
+ return anrFile;
+ } else {
+ throw new IOException("Unable to create ANR dump file: createNewFile failed");
+ }
+ }
+
+ private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+ SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsCalled();
+ }
+ ArrayList<Integer> extraPids = new ArrayList<>();
+ processCpuTracker.init();
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ignored) {
+ }
+
+ processCpuTracker.update();
+
+ // We'll take the stack crawls of just the top apps using CPU.
+ final int workingStatsNumber = processCpuTracker.countWorkingStats();
+ for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) {
+ ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+ if (lastPids.indexOfKey(stats.pid) >= 0) {
+ if (DEBUG_ANR) {
+ Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+ }
+
+ extraPids.add(stats.pid);
+ } else {
+ Slog.i(TAG,
+ "Skipping next CPU consuming process, not a java proc: "
+ + stats.pid);
+ }
+ }
+ if (latencyTracker != null) {
+ latencyTracker.processCpuTrackerMethodsReturned();
+ }
+ return extraPids;
+ }
+
+ /**
+ * Prune all trace files that are more than a day old.
+ *
+ * NOTE: It might make sense to move this functionality to tombstoned eventually, along with a
+ * shift away from anr_XX and tombstone_XX to a more descriptive name. We do it here for now
+ * since it's the system_server that creates trace files for most ANRs.
+ */
+ private static void maybePruneOldTraces(File tracesDir) {
+ final File[] files = tracesDir.listFiles();
+ if (files == null) return;
+
+ final int max = SystemProperties.getInt("tombstoned.max_anr_count", 64);
+ final long now = System.currentTimeMillis();
+ try {
+ Arrays.sort(files, Comparator.comparingLong(File::lastModified).reversed());
+ for (int i = 0; i < files.length; ++i) {
+ if (i > max || (now - files[i].lastModified()) > DAY_IN_MILLIS) {
+ if (!files[i].delete()) {
+ Slog.w(TAG, "Unable to prune stale trace file: " + files[i]);
+ }
+ }
+ }
+ } catch (IllegalArgumentException e) {
+ // The modification times changed while we were sorting. Bail...
+ // https://issuetracker.google.com/169836837
+ Slog.w(TAG, "tombstone modification times changed while sorting; not pruning", e);
+ }
+ }
+ /**
+ * Dump java traces for process {@code pid} to the specified file. If java trace dumping
+ * fails, a native backtrace is attempted. Note that the timeout {@code timeoutMs} only applies
+ * to the java section of the trace, a further {@code NATIVE_DUMP_TIMEOUT_MS} might be spent
+ * attempting to obtain native traces in the case of a failure. Returns the total time spent
+ * capturing traces.
+ */
+ private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
+ final long timeStart = SystemClock.elapsedRealtime();
+ int headerSize = writeUptimeStartHeaderForPid(pid, fileName);
+ boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
+ (int) (timeoutMs / 1000));
+ if (javaSuccess) {
+ // Check that something is in the file, actually. Try-catch should not be necessary,
+ // but better safe than sorry.
+ try {
+ long size = new File(fileName).length();
+ if ((size - headerSize) < JAVA_DUMP_MINIMUM_SIZE) {
+ Slog.w(TAG, "Successfully created Java ANR file is empty!");
+ javaSuccess = false;
+ }
+ } catch (Exception e) {
+ Slog.w(TAG, "Unable to get ANR file size", e);
+ javaSuccess = false;
+ }
+ }
+ if (!javaSuccess) {
+ Slog.w(TAG, "Dumping Java threads failed, initiating native stack dump.");
+ if (!Debug.dumpNativeBacktraceToFileTimeout(pid, fileName,
+ (NATIVE_DUMP_TIMEOUT_MS / 1000))) {
+ Slog.w(TAG, "Native stack dump failed!");
+ }
+ }
+
+ return SystemClock.elapsedRealtime() - timeStart;
+ }
+
+ private static int appendtoANRFile(String fileName, String text) {
+ try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
+ byte[] header = text.getBytes(StandardCharsets.UTF_8);
+ fos.write(header);
+ return header.length;
+ } catch (IOException e) {
+ Slog.w(TAG, "Exception writing to ANR dump file:", e);
+ return 0;
+ }
+ }
+
+ /*
+ * Writes a header containing the process id and the current system uptime.
+ */
+ private static int writeUptimeStartHeaderForPid(int pid, String fileName) {
+ return appendtoANRFile(fileName, "----- dumping pid: " + pid + " at "
+ + SystemClock.uptimeMillis() + "\n");
+ }
+
+ private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+ String logName) {
+
+ ArrayList<Integer> pids = null;
+
+ if (pidsFuture == null) {
+ return pids;
+ }
+ try {
+ pids = pidsFuture.get();
+ } catch (ExecutionException e) {
+ Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while collecting " + logName , e);
+ }
+ return pids;
+ }
+
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ad5ae5f..745e404 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4600,7 +4600,8 @@
public void onDesiredDisplayModeSpecsChanged() {
synchronized (mSyncRoot) {
mChanged = false;
- mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer);
+ mLogicalDisplayMapper.forEachLocked(mSpecsChangedConsumer,
+ /* includeDisabled= */ false);
if (mChanged) {
scheduleTraversalLocked(false);
mChanged = false;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 656882f..f5859ee 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1634,7 +1634,6 @@
mAppliedAutoBrightness = false;
brightnessAdjustmentFlags = 0;
}
-
// Use default brightness when dozing unless overridden.
if ((Float.isNaN(brightnessState))
&& Display.isDozeState(state)) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 3e01222..5306ac0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -64,6 +64,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
@@ -72,6 +73,7 @@
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController;
+import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
import com.android.server.display.layout.Layout;
@@ -209,9 +211,6 @@
// True if auto-brightness should be used.
private boolean mUseSoftwareAutoBrightnessConfig;
- // True if the brightness config has changed and the short-term model needs to be reset
- private boolean mShouldResetShortTermModel;
-
// Whether or not the color fade on screen on / off is enabled.
private final boolean mColorFadeEnabled;
@@ -296,12 +295,8 @@
// If the last recorded screen state was dozing or not.
private boolean mDozing;
- // Remembers whether certain kinds of brightness adjustments
- // were recently applied so that we can decide how to transition.
- private boolean mAppliedAutoBrightness;
private boolean mAppliedDimming;
private boolean mAppliedLowPower;
- private boolean mAppliedTemporaryAutoBrightnessAdjustment;
private boolean mAppliedThrottling;
// Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -359,6 +354,11 @@
// Tracks and manages the display state of the associated display.
private final DisplayStateController mDisplayStateController;
+
+ // Responsible for evaluating and tracking the automatic brightness relevant states.
+ // Todo: This is a temporary workaround. Ideally DPC2 should never talk to the strategies
+ private final AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
// A record of state for skipping brightness ramps.
private int mSkipRampState = RAMP_STATE_SKIP_NONE;
@@ -385,24 +385,6 @@
@Nullable
private BrightnessMappingStrategy mIdleModeBrightnessMapper;
- // The current brightness configuration.
- @Nullable
- private BrightnessConfiguration mBrightnessConfiguration;
-
- // The last auto brightness adjustment that was set by the user and not temporary. Set to
- // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
- private float mAutoBrightnessAdjustment;
-
- // The pending auto brightness adjustment that will take effect on the next power state update.
- private float mPendingAutoBrightnessAdjustment;
-
- // The temporary auto brightness adjustment. Typically set when a user is interacting with the
- // adjustment slider but hasn't settled on a choice yet. Set to
- // PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
- private float mTemporaryAutoBrightnessAdjustment;
-
- private boolean mUseAutoBrightness;
-
private boolean mIsRbcActive;
// Animators.
@@ -454,6 +436,7 @@
() -> updatePowerState(), mDisplayId, mSensorManager);
mHighBrightnessModeMetadata = hbmMetadata;
mDisplayStateController = new DisplayStateController(mDisplayPowerProximityStateController);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(context, mDisplayId);
mTag = "DisplayPowerController2[" + mDisplayId + "]";
mBrightnessThrottlingDataId = logicalDisplay.getBrightnessThrottlingDataIdLocked();
@@ -555,9 +538,6 @@
mBrightnessBucketsInDozeConfig = resources.getBoolean(
R.bool.config_displayBrightnessBucketsInDoze);
- mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
- mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mBootCompleted = bootCompleted;
}
@@ -1038,6 +1018,8 @@
mDisplayBrightnessController.setAutomaticBrightnessController(
mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy
+ .setAutomaticBrightnessController(mAutomaticBrightnessController);
mBrightnessEventRingBuffer =
new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1168,7 +1150,6 @@
final boolean mustNotify;
final int previousPolicy;
boolean mustInitialize = false;
- int brightnessAdjustmentFlags = 0;
mBrightnessReasonTemp.set(null);
mTempBrightnessEvent.reset();
SparseArray<DisplayPowerControllerInterface> displayBrightnessFollowers;
@@ -1208,7 +1189,8 @@
.updateDisplayState(mPowerRequest, mIsEnabled, mIsInTransition);
if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness
+ mScreenOffBrightnessSensorController
+ .setLightSensorEnabled(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
&& mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE
&& !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()))
&& mLeadDisplayId == Layout.NO_LEAD_DISPLAY);
@@ -1222,7 +1204,6 @@
// Animate the screen state change unless already animating.
// The transition may be deferred, so after this point we will use the
// actual state instead of the desired one.
- final int oldState = mPowerState.getScreenState();
animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
state = mPowerState.getScreenState();
@@ -1231,112 +1212,59 @@
float brightnessState = displayBrightnessState.getBrightness();
float rawBrightnessState = displayBrightnessState.getBrightness();
mBrightnessReasonTemp.set(displayBrightnessState.getBrightnessReason());
-
- final boolean autoBrightnessEnabledInDoze =
- mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig()
- && Display.isDozeState(state);
- final boolean autoBrightnessEnabled = mUseAutoBrightness
- && (state == Display.STATE_ON || autoBrightnessEnabledInDoze)
- && (Float.isNaN(brightnessState)
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_TEMPORARY
- || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_BOOST)
- && mAutomaticBrightnessController != null
- && mBrightnessReasonTemp.getReason() != BrightnessReason.REASON_FOLLOWER;
- final boolean autoBrightnessDisabledDueToDisplayOff = mUseAutoBrightness
- && !(state == Display.STATE_ON || autoBrightnessEnabledInDoze);
- final int autoBrightnessState = autoBrightnessEnabled
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
- : autoBrightnessDisabledDueToDisplayOff
- ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
- : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
-
final boolean userSetBrightnessChanged = mDisplayBrightnessController
.updateUserSetScreenBrightness();
-
- final boolean autoBrightnessAdjustmentChanged = updateAutoBrightnessAdjustment();
-
- // Use the autobrightness adjustment override if set.
- final float autoBrightnessAdjustment;
- if (!Float.isNaN(mTemporaryAutoBrightnessAdjustment)) {
- autoBrightnessAdjustment = mTemporaryAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO_TEMP;
- mAppliedTemporaryAutoBrightnessAdjustment = true;
- } else {
- autoBrightnessAdjustment = mAutoBrightnessAdjustment;
- brightnessAdjustmentFlags = BrightnessReason.ADJUSTMENT_AUTO;
- mAppliedTemporaryAutoBrightnessAdjustment = false;
- }
+ // Take note if the short term model was already active before applying the current
+ // request changes.
+ final boolean wasShortTermModelActive =
+ mAutomaticBrightnessStrategy.isShortTermModelActive();
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(state,
+ mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(),
+ brightnessState, mBrightnessReasonTemp.getReason(), mPowerRequest.policy,
+ mDisplayBrightnessController.getLastUserSetScreenBrightness(),
+ userSetBrightnessChanged);
// If the brightness is already set then it's been overridden by something other than the
// user, or is a temporary adjustment.
boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (autoBrightnessAdjustmentChanged || userSetBrightnessChanged);
- boolean wasShortTermModelActive = false;
- // Configure auto-brightness.
- if (mAutomaticBrightnessController != null) {
- wasShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
- mAutomaticBrightnessController.configure(autoBrightnessState,
- mBrightnessConfiguration,
- mDisplayBrightnessController.getLastUserSetScreenBrightness(),
- userSetBrightnessChanged, autoBrightnessAdjustment,
- autoBrightnessAdjustmentChanged, mPowerRequest.policy,
- mShouldResetShortTermModel);
- mShouldResetShortTermModel = false;
- }
- mHbmController.setAutoBrightnessEnabled(mUseAutoBrightness
+ && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+ || userSetBrightnessChanged);
+
+ mHbmController.setAutoBrightnessEnabled(mAutomaticBrightnessStrategy
+ .shouldUseAutoBrightness()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
: AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED);
- if (mBrightnessTracker != null) {
- mBrightnessTracker.setShouldCollectColorSample(mBrightnessConfiguration != null
- && mBrightnessConfiguration.shouldCollectColorSamples());
- }
-
boolean updateScreenBrightnessSetting = false;
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
boolean slowChange = false;
+ int brightnessAdjustmentFlags = 0;
if (Float.isNaN(brightnessState)) {
- float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
- if (autoBrightnessEnabled) {
- rawBrightnessState = mAutomaticBrightnessController
- .getRawAutomaticScreenBrightness();
- brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness(
- mTempBrightnessEvent);
- newAutoBrightnessAdjustment =
- mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
- }
- if (BrightnessUtils.isValidBrightnessValue(brightnessState)
- || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
- // Use current auto-brightness value and slowly adjust to changes.
- brightnessState = clampScreenBrightness(brightnessState);
- if (mAppliedAutoBrightness && !autoBrightnessAdjustmentChanged) {
- slowChange = true; // slowly adapt to auto-brightness
+ if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
+ brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness();
+ if (BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT) {
+ rawBrightnessState = mAutomaticBrightnessController
+ .getRawAutomaticScreenBrightness();
+ brightnessState = clampScreenBrightness(brightnessState);
+ // slowly adapt to auto-brightness
+ slowChange = mAutomaticBrightnessStrategy.hasAppliedAutoBrightness()
+ && !mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged();
+ brightnessAdjustmentFlags =
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags();
+ updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
+ mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
+ if (mScreenOffBrightnessSensorController != null) {
+ mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
+ }
}
- updateScreenBrightnessSetting = currentBrightnessSetting != brightnessState;
- mAppliedAutoBrightness = true;
- mBrightnessReasonTemp.setReason(BrightnessReason.REASON_AUTOMATIC);
- if (mScreenOffBrightnessSensorController != null) {
- mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
- }
- } else {
- mAppliedAutoBrightness = false;
- }
- if (autoBrightnessAdjustment != newAutoBrightnessAdjustment) {
- // If the autobrightness controller has decided to change the adjustment value
- // used, make sure that's reflected in settings.
- putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
- } else {
- // Adjustment values resulted in no change
- brightnessAdjustmentFlags = 0;
}
} else {
// Any non-auto-brightness values such as override or temporary should still be subject
// to clamping so that they don't go beyond the current max as specified by HBM
// Controller.
brightnessState = clampScreenBrightness(brightnessState);
- mAppliedAutoBrightness = false;
- brightnessAdjustmentFlags = 0;
}
// Use default brightness when dozing unless overridden.
@@ -1349,7 +1277,7 @@
// The ALS is not available yet - use the screen off sensor to determine the initial
// brightness
- if (Float.isNaN(brightnessState) && autoBrightnessEnabled
+ if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
&& mScreenOffBrightnessSensorController != null) {
rawBrightnessState =
mScreenOffBrightnessSensorController.getAutomaticScreenBrightness();
@@ -1466,7 +1394,8 @@
boolean brightnessAdjusted = false;
final boolean brightnessIsTemporary =
(mBrightnessReason.getReason() == BrightnessReason.REASON_TEMPORARY)
- || mAppliedTemporaryAutoBrightnessAdjustment;
+ || mAutomaticBrightnessStrategy
+ .isTemporaryAutoBrightnessAdjustmentApplied();
if (!mPendingScreenOff) {
if (mSkipScreenOnBrightnessRamp) {
if (state == Display.STATE_ON) {
@@ -1524,6 +1453,7 @@
final float currentBrightness = mPowerState.getScreenBrightness();
final float currentSdrBrightness = mPowerState.getSdrScreenBrightness();
+
if (BrightnessUtils.isValidBrightnessValue(animateValue)
&& (animateValue != currentBrightness
|| sdrAnimateValue != currentSdrBrightness)) {
@@ -1605,7 +1535,8 @@
mTempBrightnessEvent.setWasShortTermModelActive(wasShortTermModelActive);
mTempBrightnessEvent.setDisplayBrightnessStrategyName(displayBrightnessState
.getDisplayBrightnessStrategyName());
- mTempBrightnessEvent.setAutomaticBrightnessEnabled(mUseAutoBrightness);
+ mTempBrightnessEvent.setAutomaticBrightnessEnabled(mAutomaticBrightnessStrategy
+ .shouldUseAutoBrightness());
// Temporary is what we use during slider interactions. We avoid logging those so that
// we don't spam logcat when the slider is being used.
boolean tempToTempTransition =
@@ -2151,13 +2082,12 @@
mDisplayBrightnessController
.setPendingScreenBrightness(mDisplayBrightnessController
.getScreenBrightnessSetting());
- mPendingAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(userSwitch);
if (userSwitch) {
// Don't treat user switches as user initiated change.
mDisplayBrightnessController
.setAndNotifyCurrentScreenBrightness(mDisplayBrightnessController
.getPendingScreenBrightness());
- updateAutoBrightnessAdjustment();
if (mAutomaticBrightnessController != null) {
mAutomaticBrightnessController.resetShortTermModel();
}
@@ -2171,8 +2101,8 @@
Settings.System.SCREEN_BRIGHTNESS_MODE,
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
mHandler.postAtTime(() -> {
- mUseAutoBrightness = screenBrightnessModeSetting
- == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(screenBrightnessModeSetting
+ == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
updatePowerState();
}, mClock.uptimeMillis());
}
@@ -2220,33 +2150,10 @@
sendUpdatePowerState();
}
- private void putAutoBrightnessAdjustmentSetting(float adjustment) {
- if (mDisplayId == Display.DEFAULT_DISPLAY) {
- mAutoBrightnessAdjustment = adjustment;
- Settings.System.putFloatForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
- UserHandle.USER_CURRENT);
- }
- }
-
- private boolean updateAutoBrightnessAdjustment() {
- if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
- return false;
- }
- if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
- mPendingAutoBrightnessAdjustment = Float.NaN;
- return false;
- }
- mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
- mPendingAutoBrightnessAdjustment = Float.NaN;
- mTemporaryAutoBrightnessAdjustment = Float.NaN;
- return true;
- }
-
private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
boolean wasShortTermModelActive) {
final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
- if (mUseAutoBrightness && brightnessInNits >= 0.0f
+ if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness() && brightnessInNits >= 0.0f
&& mAutomaticBrightnessController != null && mBrightnessTracker != null) {
// We only want to track changes on devices that can actually map the display backlight
// values into a physical brightness unit since the value provided by the API is in
@@ -2329,16 +2236,10 @@
pw.println();
pw.println("Display Power Controller Thread State:");
pw.println(" mPowerRequest=" + mPowerRequest);
- pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mBrightnessReason=" + mBrightnessReason);
- pw.println(" mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
- pw.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
- pw.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
pw.println(" mAppliedDimming=" + mAppliedDimming);
pw.println(" mAppliedLowPower=" + mAppliedLowPower);
pw.println(" mAppliedThrottling=" + mAppliedThrottling);
- pw.println(" mAppliedTemporaryAutoBrightnessAdjustment="
- + mAppliedTemporaryAutoBrightnessAdjustment);
pw.println(" mDozing=" + mDozing);
pw.println(" mSkipRampState=" + skipRampStateToString(mSkipRampState));
pw.println(" mScreenOnBlockStartRealTime=" + mScreenOnBlockStartRealTime);
@@ -2349,6 +2250,8 @@
pw.println(" mReportedToPolicy="
+ reportedToPolicyToString(mReportedScreenStateToPolicy));
pw.println(" mIsRbcActive=" + mIsRbcActive);
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ mAutomaticBrightnessStrategy.dump(ipw);
if (mScreenBrightnessRampAnimator != null) {
pw.println(" mScreenBrightnessRampAnimator.isAnimating()="
@@ -2580,8 +2483,15 @@
}
break;
case MSG_CONFIGURE_BRIGHTNESS:
- mBrightnessConfiguration = (BrightnessConfiguration) msg.obj;
- mShouldResetShortTermModel = msg.arg1 == 1;
+ BrightnessConfiguration brightnessConfiguration =
+ (BrightnessConfiguration) msg.obj;
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(brightnessConfiguration,
+ msg.arg1 == 1);
+ if (mBrightnessTracker != null) {
+ mBrightnessTracker
+ .setShouldCollectColorSample(brightnessConfiguration != null
+ && brightnessConfiguration.shouldCollectColorSamples());
+ }
updatePowerState();
break;
@@ -2593,7 +2503,8 @@
break;
case MSG_SET_TEMPORARY_AUTO_BRIGHTNESS_ADJUSTMENT:
- mTemporaryAutoBrightnessAdjustment = Float.intBitsToFloat(msg.arg1);
+ mAutomaticBrightnessStrategy
+ .setTemporaryAutoBrightnessAdjustment(Float.intBitsToFloat(msg.arg1));
updatePowerState();
break;
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 250ba43..424eedc 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -302,9 +302,16 @@
}
public void forEachLocked(Consumer<LogicalDisplay> consumer) {
+ forEachLocked(consumer, /* includeDisabled= */ true);
+ }
+
+ public void forEachLocked(Consumer<LogicalDisplay> consumer, boolean includeDisabled) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
- consumer.accept(mLogicalDisplays.valueAt(i));
+ LogicalDisplay display = mLogicalDisplays.valueAt(i);
+ if (display.isEnabledLocked() || includeDisabled) {
+ consumer.accept(display);
+ }
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
new file mode 100644
index 0000000..f6cf866
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.strategy;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.BrightnessConfiguration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.Display;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Helps manage the brightness based on the ambient environment (Ambient Light/lux sensor) using
+ * mappings from lux to nits to brightness, configured in the
+ * {@link com.android.server.display.DisplayDeviceConfig} class. This class inherently assumes
+ * that it is being executed from the power thread, and hence doesn't synchronize
+ * any of its resources
+ */
+public class AutomaticBrightnessStrategy {
+ private final Context mContext;
+ // The DisplayId of the associated logical display
+ private final int mDisplayId;
+ // The last auto brightness adjustment that was set by the user and is not temporary. Set to
+ // Float.NaN when an auto-brightness adjustment hasn't been recorded yet.
+ private float mAutoBrightnessAdjustment;
+ // The pending auto brightness adjustment that will take effect on the next power state update.
+ private float mPendingAutoBrightnessAdjustment;
+ // The temporary auto brightness adjustment. This was historically used when a user interacts
+ // with the adjustment slider but hasn't settled on a choice yet.
+ // Set to PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
+ private float mTemporaryAutoBrightnessAdjustment;
+ // Indicates if the temporary auto brightness adjustment has been applied while updating the
+ // associated display brightness
+ private boolean mAppliedTemporaryAutoBrightnessAdjustment;
+ // Indicates if the auto brightness adjustment has happened.
+ private boolean mAutoBrightnessAdjustmentChanged;
+ // Indicates the reasons for the auto-brightness adjustment
+ private int mAutoBrightnessAdjustmentReasonsFlags = 0;
+ // Indicates if the short term model should be reset before fetching the new brightness
+ // Todo(273543270): Short term model is an internal information of
+ // AutomaticBrightnessController and shouldn't be exposed outside of that class
+ private boolean mShouldResetShortTermModel = false;
+ // Remembers whether the auto-brightness has been applied in the latest brightness update.
+ private boolean mAppliedAutoBrightness = false;
+ // The controller for the automatic brightness level.
+ @Nullable
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+ // The system setting denoting if the auto-brightness for the current user is enabled or not
+ private boolean mUseAutoBrightness = false;
+ // Indicates if the auto-brightness is currently enabled or not. It's possible that even if
+ // the user has enabled the auto-brightness from the settings, it is disabled because the
+ // display is off
+ private boolean mIsAutoBrightnessEnabled = false;
+ // If the auto-brightness model for the last manual changes done by the user.
+ private boolean mIsShortTermModelActive = false;
+
+ // The BrightnessConfiguration currently being used
+ // Todo(273543270): BrightnessConfiguration is an internal implementation detail of
+ // AutomaticBrightnessController, and AutomaticBrightnessStrategy shouldn't be aware of its
+ // existence.
+ @Nullable
+ private BrightnessConfiguration mBrightnessConfiguration;
+
+ public AutomaticBrightnessStrategy(Context context, int displayId) {
+ mContext = context;
+ mDisplayId = displayId;
+ mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
+ mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ }
+
+ /**
+ * Sets up the automatic brightness states of this class. Also configures
+ * AutomaticBrightnessController accounting for any manual changes made by the user.
+ */
+ public void setAutoBrightnessState(int targetDisplayState,
+ boolean allowAutoBrightnessWhileDozingConfig,
+ float brightnessState, int brightnessReason, int policy,
+ float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) {
+ final boolean autoBrightnessEnabledInDoze =
+ allowAutoBrightnessWhileDozingConfig
+ && Display.isDozeState(targetDisplayState);
+ mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
+ && (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
+ && (Float.isNaN(brightnessState)
+ || brightnessReason == BrightnessReason.REASON_TEMPORARY
+ || brightnessReason == BrightnessReason.REASON_BOOST)
+ && mAutomaticBrightnessController != null
+ && brightnessReason != BrightnessReason.REASON_FOLLOWER;
+ final boolean autoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
+ && !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
+ final int autoBrightnessState = mIsAutoBrightnessEnabled
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
+ : autoBrightnessDisabledDueToDisplayOff
+ ? AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE
+ : AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
+
+ accommodateUserBrightnessChanges(userSetBrightnessChanged, lastUserSetScreenBrightness,
+ policy, mBrightnessConfiguration, autoBrightnessState);
+ }
+
+ public boolean isAutoBrightnessEnabled() {
+ return mIsAutoBrightnessEnabled;
+ }
+
+ /**
+ * Updates the {@link BrightnessConfiguration} that is currently being used by the associated
+ * display.
+ */
+ public void setBrightnessConfiguration(BrightnessConfiguration brightnessConfiguration,
+ boolean shouldResetShortTermModel) {
+ mBrightnessConfiguration = brightnessConfiguration;
+ setShouldResetShortTermModel(shouldResetShortTermModel);
+ }
+
+ /**
+ * Promotes the pending auto-brightness adjustments which are yet to be applied to the current
+ * adjustments. Note that this is not applying the new adjustments to the AutoBrightness mapping
+ * strategies, but is only accommodating the changes in this class.
+ */
+ public boolean processPendingAutoBrightnessAdjustments() {
+ mAutoBrightnessAdjustmentChanged = false;
+ if (Float.isNaN(mPendingAutoBrightnessAdjustment)) {
+ return false;
+ }
+ if (mAutoBrightnessAdjustment == mPendingAutoBrightnessAdjustment) {
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ return false;
+ }
+ mAutoBrightnessAdjustment = mPendingAutoBrightnessAdjustment;
+ mPendingAutoBrightnessAdjustment = Float.NaN;
+ mTemporaryAutoBrightnessAdjustment = Float.NaN;
+ mAutoBrightnessAdjustmentChanged = true;
+ return true;
+ }
+
+ /**
+ * Updates the associated AutomaticBrightnessController
+ */
+ public void setAutomaticBrightnessController(
+ AutomaticBrightnessController automaticBrightnessController) {
+ if (automaticBrightnessController == mAutomaticBrightnessController) {
+ return;
+ }
+ if (mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.stop();
+ }
+ mAutomaticBrightnessController = automaticBrightnessController;
+ }
+
+ /**
+ * Returns if the auto-brightness of the associated display has been enabled or not
+ */
+ public boolean shouldUseAutoBrightness() {
+ return mUseAutoBrightness;
+ }
+
+ /**
+ * Sets the auto-brightness state of the associated display. Called when the user makes a change
+ * in the system setting to enable/disable the auto-brightness.
+ */
+ public void setUseAutoBrightness(boolean useAutoBrightness) {
+ mUseAutoBrightness = useAutoBrightness;
+ }
+
+ /**
+ * Returns if the user made brightness change events(Typically when they interact with the
+ * brightness slider) were accommodated in the auto-brightness mapping strategies. This doesn't
+ * account for the latest changes that have been made by the user.
+ */
+ public boolean isShortTermModelActive() {
+ return mIsShortTermModelActive;
+ }
+
+ /**
+ * Sets the pending auto-brightness adjustments in the system settings. Executed
+ * when there is a change in the brightness system setting, or when there is a user switch.
+ */
+ public void updatePendingAutoBrightnessAdjustments(boolean userSwitch) {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ mPendingAutoBrightnessAdjustment = Float.isNaN(adj) ? Float.NaN
+ : BrightnessUtils.clampAbsoluteBrightness(adj);
+ if (userSwitch) {
+ processPendingAutoBrightnessAdjustments();
+ }
+ }
+
+ /**
+ * Sets the temporary auto-brightness adjustments
+ */
+ public void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mTemporaryAutoBrightnessAdjustment = temporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Dumps the state of this class.
+ */
+ public void dump(PrintWriter writer) {
+ writer.println("AutomaticBrightnessStrategy:");
+ writer.println(" mDisplayId=" + mDisplayId);
+ writer.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
+ writer.println(" mPendingAutoBrightnessAdjustment=" + mPendingAutoBrightnessAdjustment);
+ writer.println(
+ " mTemporaryAutoBrightnessAdjustment=" + mTemporaryAutoBrightnessAdjustment);
+ writer.println(" mShouldResetShortTermModel=" + mShouldResetShortTermModel);
+ writer.println(" mAppliedAutoBrightness=" + mAppliedAutoBrightness);
+ writer.println(" mAutoBrightnessAdjustmentChanged=" + mAutoBrightnessAdjustmentChanged);
+ writer.println(" mAppliedTemporaryAutoBrightnessAdjustment="
+ + mAppliedTemporaryAutoBrightnessAdjustment);
+ writer.println(" mUseAutoBrightness=" + mUseAutoBrightness);
+ writer.println(" mWasShortTermModelActive=" + mIsShortTermModelActive);
+ writer.println(" mAutoBrightnessAdjustmentReasonsFlags="
+ + mAutoBrightnessAdjustmentReasonsFlags);
+ }
+
+ /**
+ * Indicates if any auto-brightness adjustments have happened since the last auto-brightness was
+ * set.
+ */
+ public boolean getAutoBrightnessAdjustmentChanged() {
+ return mAutoBrightnessAdjustmentChanged;
+ }
+
+ /**
+ * Returns whether the latest temporary auto-brightness adjustments have been applied or not
+ */
+ public boolean isTemporaryAutoBrightnessAdjustmentApplied() {
+ return mAppliedTemporaryAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Evaluates the target automatic brightness of the associated display.
+ */
+ public float getAutomaticScreenBrightness() {
+ float brightness = (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightness()
+ : PowerManager.BRIGHTNESS_INVALID_FLOAT;
+ adjustAutomaticBrightnessStateIfValid(brightness);
+ return brightness;
+ }
+
+ /**
+ * Gets the auto-brightness adjustment flag change reason
+ */
+ public int getAutoBrightnessAdjustmentReasonsFlags() {
+ return mAutoBrightnessAdjustmentReasonsFlags;
+ }
+
+ /**
+ * Returns if the auto brightness has been applied
+ */
+ public boolean hasAppliedAutoBrightness() {
+ return mAppliedAutoBrightness;
+ }
+
+ /**
+ * Used to adjust the state of this class when the automatic brightness value for the
+ * associated display is valid
+ */
+ @VisibleForTesting
+ void adjustAutomaticBrightnessStateIfValid(float brightnessState) {
+ mAutoBrightnessAdjustmentReasonsFlags = isTemporaryAutoBrightnessAdjustmentApplied()
+ ? BrightnessReason.ADJUSTMENT_AUTO_TEMP
+ : BrightnessReason.ADJUSTMENT_AUTO;
+ mAppliedAutoBrightness = BrightnessUtils.isValidBrightnessValue(brightnessState)
+ || brightnessState == PowerManager.BRIGHTNESS_OFF_FLOAT;
+ float newAutoBrightnessAdjustment =
+ (mAutomaticBrightnessController != null)
+ ? mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()
+ : 0.0f;
+ if (!Float.isNaN(newAutoBrightnessAdjustment)
+ && mAutoBrightnessAdjustment != newAutoBrightnessAdjustment) {
+ // If the auto-brightness controller has decided to change the adjustment value
+ // used, make sure that's reflected in settings.
+ putAutoBrightnessAdjustmentSetting(newAutoBrightnessAdjustment);
+ } else {
+ mAutoBrightnessAdjustmentReasonsFlags = 0;
+ }
+ }
+
+ /**
+ * Sets up the system to reset the short term model. Note that this will not reset the model
+ * right away, but ensures that the reset happens whenever the next brightness change happens
+ */
+ @VisibleForTesting
+ void setShouldResetShortTermModel(boolean shouldResetShortTermModel) {
+ mShouldResetShortTermModel = shouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ boolean shouldResetShortTermModel() {
+ return mShouldResetShortTermModel;
+ }
+
+ @VisibleForTesting
+ float getAutoBrightnessAdjustment() {
+ return mAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getPendingAutoBrightnessAdjustment() {
+ return mPendingAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ float getTemporaryAutoBrightnessAdjustment() {
+ return mTemporaryAutoBrightnessAdjustment;
+ }
+
+ @VisibleForTesting
+ void putAutoBrightnessAdjustmentSetting(float adjustment) {
+ if (mDisplayId == Display.DEFAULT_DISPLAY) {
+ mAutoBrightnessAdjustment = adjustment;
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, adjustment,
+ UserHandle.USER_CURRENT);
+ }
+ }
+
+ /**
+ * Sets if the auto-brightness is applied on the latest brightness change.
+ */
+ @VisibleForTesting
+ void setAutoBrightnessApplied(boolean autoBrightnessApplied) {
+ mAppliedAutoBrightness = autoBrightnessApplied;
+ }
+
+ /**
+ * Accommodates the latest manual changes made by the user. Also updates {@link
+ * AutomaticBrightnessController} about the changes and configures it accordingly.
+ */
+ @VisibleForTesting
+ void accommodateUserBrightnessChanges(boolean userSetBrightnessChanged,
+ float lastUserSetScreenBrightness, int policy,
+ BrightnessConfiguration brightnessConfiguration, int autoBrightnessState) {
+ // Update the pending auto-brightness adjustments if any. This typically checks and adjusts
+ // the state of the class if the user moves the brightness slider and has settled to a
+ // different value
+ processPendingAutoBrightnessAdjustments();
+ // Update the temporary auto-brightness adjustments if any. This typically checks and
+ // adjusts the state of this class if the user is in the process of moving the brightness
+ // slider, but hasn't settled to any value yet
+ float autoBrightnessAdjustment = updateTemporaryAutoBrightnessAdjustments();
+ mIsShortTermModelActive = false;
+ // Configure auto-brightness.
+ if (mAutomaticBrightnessController != null) {
+ // Accommodate user changes if any in the auto-brightness model
+ mAutomaticBrightnessController.configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, autoBrightnessAdjustment,
+ mAutoBrightnessAdjustmentChanged, policy, mShouldResetShortTermModel);
+ mShouldResetShortTermModel = false;
+ // We take note if the user brightness point is still being used in the current
+ // auto-brightness model.
+ mIsShortTermModelActive = mAutomaticBrightnessController.hasUserDataPoints();
+ }
+ }
+
+ /**
+ * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet.
+ * Temporary brightness adjustments happen when the user moves the brightness slider in the
+ * auto-brightness mode, but hasn't settled to a value yet
+ */
+ private float updateTemporaryAutoBrightnessAdjustments() {
+ mAppliedTemporaryAutoBrightnessAdjustment =
+ !Float.isNaN(mTemporaryAutoBrightnessAdjustment);
+ // We do not update the mAutoBrightnessAdjustment with mTemporaryAutoBrightnessAdjustment
+ // since we have not settled to a value yet
+ return mAppliedTemporaryAutoBrightnessAdjustment
+ ? mTemporaryAutoBrightnessAdjustment : mAutoBrightnessAdjustment;
+ }
+
+ /**
+ * Returns the auto-brightness adjustment that is set in the system setting.
+ */
+ private float getAutoBrightnessAdjustmentSetting() {
+ final float adj = Settings.System.getFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f, UserHandle.USER_CURRENT);
+ return Float.isNaN(adj) ? 0.0f : BrightnessUtils.clampAbsoluteBrightness(adj);
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
index abb8439..9058c98 100644
--- a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
@@ -80,7 +80,7 @@
protected void handleEarcStateChange(@Constants.EarcStatus int status) {
int oldEarcStatus;
synchronized (mLock) {
- HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus,
+ HdmiLogger.debug("eARC state change [old:%b new %b]", mEarcStatus,
status);
oldEarcStatus = mEarcStatus;
mEarcStatus = status;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index efc4f11..d0669e7 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -547,6 +547,10 @@
mBatteryController.systemRunning();
mKeyboardBacklightController.systemRunning();
mKeyRemapper.systemRunning();
+
+ mNative.setStylusPointerIconEnabled(
+ Objects.requireNonNull(mContext.getSystemService(InputManager.class))
+ .isStylusPointerIconEnabled());
}
private void reloadDeviceAliases() {
@@ -2749,13 +2753,6 @@
return null;
}
- // Native callback.
- @SuppressWarnings("unused")
- private boolean isStylusPointerIconEnabled() {
- return Objects.requireNonNull(mContext.getSystemService(InputManager.class))
- .isStylusPointerIconEnabled();
- }
-
private static class PointerDisplayIdChangedArgs {
final int mPointerDisplayId;
final float mXPosition;
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 4d4a87e..72c7dad 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1261,30 +1261,45 @@
private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
@NonNull String languageTag) {
- final int[] scriptsFromLanguageTag = UScript.getCode(Locale.forLanguageTag(languageTag));
- if (scriptsFromLanguageTag.length == 0) {
- // If no scripts inferred from languageTag then allowing the layout
- return true;
- }
- LocaleList locales = layout.getLocales();
- if (locales.isEmpty()) {
+ LocaleList layoutLocales = layout.getLocales();
+ if (layoutLocales.isEmpty()) {
// KCM file doesn't have an associated language tag. This can be from
// a 3rd party app so need to include it as a potential layout.
return true;
}
- for (int i = 0; i < locales.size(); i++) {
- final Locale locale = locales.get(i);
- if (locale == null) {
- continue;
- }
- int[] scripts = UScript.getCode(locale);
- if (scripts != null && haveCommonValue(scripts, scriptsFromLanguageTag)) {
+ // Match derived Script codes
+ final int[] scriptsFromLanguageTag = getScriptCodes(Locale.forLanguageTag(languageTag));
+ if (scriptsFromLanguageTag.length == 0) {
+ // If no scripts inferred from languageTag then allowing the layout
+ return true;
+ }
+ for (int i = 0; i < layoutLocales.size(); i++) {
+ final Locale locale = layoutLocales.get(i);
+ int[] scripts = getScriptCodes(locale);
+ if (haveCommonValue(scripts, scriptsFromLanguageTag)) {
return true;
}
}
return false;
}
+ private static int[] getScriptCodes(@Nullable Locale locale) {
+ if (locale == null) {
+ return new int[0];
+ }
+ if (!TextUtils.isEmpty(locale.getScript())) {
+ int scriptCode = UScript.getCodeFromName(locale.getScript());
+ if (scriptCode != UScript.INVALID_CODE) {
+ return new int[]{scriptCode};
+ }
+ }
+ int[] scripts = UScript.getCode(locale);
+ if (scripts != null) {
+ return scripts;
+ }
+ return new int[0];
+ }
+
private static boolean haveCommonValue(int[] arr1, int[] arr2) {
for (int a1 : arr1) {
for (int a2 : arr2) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 5395302d..a0918e4 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -234,6 +234,9 @@
*/
float[] getMouseCursorPosition();
+ /** Set whether showing a pointer icon for styluses is enabled. */
+ void setStylusPointerIconEnabled(boolean enabled);
+
/** The native implementation of InputManagerService methods. */
class NativeImpl implements NativeInputManagerService {
/** Pointer to native input manager service object, used by native code. */
@@ -478,5 +481,8 @@
@Override
public native float[] getMouseCursorPosition();
+
+ @Override
+ public native void setStylusPointerIconEnabled(boolean enabled);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index ceb9706..61fe654 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -284,7 +284,7 @@
void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
- if (state != null && newState.hasEdiorFocused()) {
+ if (state != null && newState.hasEditorFocused()) {
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
newState.setRequestedImeVisible(state.mRequestedImeVisible);
@@ -340,7 +340,7 @@
// state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation).
// Because the app might leverage these flags to hide soft-keyboard with showing their own
// UI for input.
- if (state.hasEdiorFocused() && shouldRestoreImeVisibility(state)) {
+ if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) {
if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
// Inherit the last requested IME visible state when the target window is still
// focused with an editor.
@@ -352,7 +352,7 @@
switch (softInputVisibility) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
- if (state.hasImeFocusChanged() && (!state.hasEdiorFocused() || !doAutoShow)) {
+ if (state.hasImeFocusChanged() && (!state.hasEditorFocused() || !doAutoShow)) {
if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
@@ -361,7 +361,7 @@
return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
}
- } else if (state.hasEdiorFocused() && doAutoShow && isForwardNavigation) {
+ } else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) {
// There is a focus view, and we are navigating forward
// into the window, so show the input window for the user.
// We only do this automatically if the window can resize
@@ -437,7 +437,7 @@
SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
}
}
- if (!state.hasEdiorFocused() && mInputShown && state.isStartInputByGainFocus()
+ if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus()
&& mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) {
// Hide the soft-keyboard when the system do nothing for softInputModeState
// of the window being gained focus without an editor. This behavior benefits
@@ -620,7 +620,7 @@
return mImeFocusChanged;
}
- boolean hasEdiorFocused() {
+ boolean hasEditorFocused() {
return mHasFocusedEditor;
}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 16155a0..5ea2ca4 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -17,6 +17,8 @@
package com.android.server.media;
import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -55,6 +57,8 @@
import android.util.Log;
import android.view.KeyEvent;
+import com.android.server.LocalServices;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -422,6 +426,13 @@
*/
@Override
public void close() {
+ // Log the session's active state
+ // to measure usage of foreground service resources
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
synchronized (mLock) {
if (mDestroyed) {
return;
@@ -884,8 +895,22 @@
@Override
public void setActive(boolean active) throws RemoteException {
+ // Log the session's active state
+ // to measure usage of foreground service resources
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+ if (active) {
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiBegin(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
+ } else {
+ LocalServices.getService(ActivityManagerInternal.class)
+ .logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
+ callingUid, callingPid);
+ }
+
mIsActive = active;
- final long token = Binder.clearCallingIdentity();
+ long token = Binder.clearCallingIdentity();
try {
mService.onSessionActiveStateChanged(MediaSessionRecord.this);
} finally {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 431925c..a4eb417 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3328,10 +3328,10 @@
}
if (!isUiContext && displayId == Display.DEFAULT_DISPLAY
- && UserManager.isVisibleBackgroundUsersEnabled()) {
- // When the caller is a visible background user using a non-ui context (like the
+ && mUm.isVisibleBackgroundUsersSupported()) {
+ // When the caller is a visible background user using a non-UI context (like the
// application context), the Toast must be displayed in the display the user was
- // started visible on
+ // started visible on.
int userId = UserHandle.getUserId(callingUid);
int userDisplayId = mUmInternal.getMainDisplayAssignedToUser(userId);
if (displayId != userDisplayId) {
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index e698c4b..0605345 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -39,14 +39,18 @@
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.LocalLog;
import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
import com.android.server.SystemConfig;
+import com.android.server.utils.Slogf;
import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.Objects;
import java.util.OptionalInt;
@@ -56,7 +60,11 @@
* <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
*/
class BugreportManagerServiceImpl extends IDumpstate.Stub {
+
+ private static final int LOCAL_LOG_SIZE = 20;
private static final String TAG = "BugreportManagerService";
+ private static final boolean DEBUG = false;
+
private static final String BUGREPORT_SERVICE = "bugreportd";
private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
@@ -64,12 +72,22 @@
private final Context mContext;
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
- private final ArraySet<String> mBugreportWhitelistedPackages;
+ private final ArraySet<String> mBugreportAllowlistedPackages;
private final BugreportFileManager mBugreportFileManager;
+
@GuardedBy("mLock")
private OptionalInt mPreDumpedDataUid = OptionalInt.empty();
+ // Attributes below are just Used for dump() purposes
+ @Nullable
+ @GuardedBy("mLock")
+ private DumpstateListener mCurrentDumpstateListener;
+ @GuardedBy("mLock")
+ private int mNumberFinishedBugreports;
+ @GuardedBy("mLock")
+ private final LocalLog mFinishedBugreports = new LocalLog(LOCAL_LOG_SIZE);
+
/** Helper class for associating previously generated bugreports with their callers. */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
static class BugreportFileManager {
@@ -77,11 +95,8 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
- private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles;
-
- BugreportFileManager() {
- mBugreportFiles = new ArrayMap<>();
- }
+ private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
+ new ArrayMap<>();
/**
* Checks that a given file was generated on behalf of the given caller. If the file was
@@ -159,11 +174,9 @@
mAppOps = mContext.getSystemService(AppOpsManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
mBugreportFileManager = new BugreportFileManager();
- mBugreportWhitelistedPackages =
- injector.getAllowlistedPackages();
+ mBugreportAllowlistedPackages = injector.getAllowlistedPackages();
}
-
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
public void preDumpUiData(String callingPackage) {
@@ -196,6 +209,7 @@
Binder.restoreCallingIdentity(identity);
}
+ Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
bugreportMode, bugreportFlags, listener, isScreenshotRequested);
@@ -208,6 +222,7 @@
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */);
+ Slogf.i(TAG, "Cancelling bugreport for %s / %d", callingPackage, callingUid);
synchronized (mLock) {
IDumpstate ds = getDumpstateBinderServiceLocked();
if (ds == null) {
@@ -234,6 +249,7 @@
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, false);
+ Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
try {
mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
new Pair<>(callingUid, callingPackage), bugreportFile);
@@ -260,8 +276,9 @@
}
// Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds,
- new Pair<>(callingUid, callingPackage));
+ DumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);
+ setCurrentDumpstateListenerLocked(myListener);
try {
ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
bugreportFile, myListener);
@@ -271,6 +288,16 @@
}
}
+ @GuardedBy("mLock")
+ private void setCurrentDumpstateListenerLocked(DumpstateListener listener) {
+ if (mCurrentDumpstateListener != null) {
+ Slogf.w(TAG, "setCurrentDumpstateListenerLocked(%s): called when "
+ + "mCurrentDumpstateListener is already set (%s)", listener,
+ mCurrentDumpstateListener);
+ }
+ mCurrentDumpstateListener = listener;
+ }
+
private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
if (mode != BugreportParams.BUGREPORT_MODE_FULL
&& mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
@@ -299,7 +326,7 @@
// To gain access through the DUMP permission, the OEM has to allow this package explicitly
// via sysconfig and privileged permissions.
- if (mBugreportWhitelistedPackages.contains(callingPackage)
+ if (mBugreportAllowlistedPackages.contains(callingPackage)
&& mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
== PackageManager.PERMISSION_GRANTED) {
return;
@@ -436,7 +463,7 @@
}
}
- boolean isConsentDeferred =
+ boolean reportFinishedFile =
(bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;
IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
@@ -446,9 +473,9 @@
return;
}
- // Wrap the listener so we can intercept binder events directly.
- IDumpstateListener myListener = new DumpstateListener(listener, ds,
- isConsentDeferred ? new Pair<>(callingUid, callingPackage) : null);
+ DumpstateListener myListener = new DumpstateListener(listener, ds,
+ new Pair<>(callingUid, callingPackage), reportFinishedFile);
+ setCurrentDumpstateListenerLocked(myListener);
try {
ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
bugreportFlags, myListener, isScreenshotRequested);
@@ -522,6 +549,56 @@
SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
}
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ pw.printf("Allow-listed packages: %s\n", mBugreportAllowlistedPackages);
+
+ synchronized (mLock) {
+ pw.print("Pre-dumped data UID: ");
+ if (mPreDumpedDataUid.isEmpty()) {
+ pw.println("none");
+ } else {
+ pw.println(mPreDumpedDataUid.getAsInt());
+ }
+
+ if (mCurrentDumpstateListener == null) {
+ pw.println("Not taking a bug report");
+ } else {
+ mCurrentDumpstateListener.dump(pw);
+ }
+
+ if (mNumberFinishedBugreports == 0) {
+ pw.println("No finished bugreports");
+ } else {
+ pw.printf("%d finished bugreport%s. Last %d:\n", mNumberFinishedBugreports,
+ (mNumberFinishedBugreports > 1 ? "s" : ""),
+ Math.min(mNumberFinishedBugreports, LOCAL_LOG_SIZE));
+ mFinishedBugreports.dump(" ", pw);
+ }
+ }
+
+ synchronized (mBugreportFileManager.mLock) {
+ int numberFiles = mBugreportFileManager.mBugreportFiles.size();
+ pw.printf("%d pending file%s", numberFiles, (numberFiles > 1 ? "s" : ""));
+ if (numberFiles > 0) {
+ for (int i = 0; i < numberFiles; i++) {
+ Pair<Integer, String> caller = mBugreportFileManager.mBugreportFiles.keyAt(i);
+ ArraySet<String> files = mBugreportFileManager.mBugreportFiles.valueAt(i);
+ pw.printf(" %s: %s\n", callerToString(caller), files);
+ }
+ } else {
+ pw.println();
+ }
+ }
+ }
+
+ private static String callerToString(@Nullable Pair<Integer, String> caller) {
+ return (caller == null) ? "N/A" : caller.second + "/" + caller.first;
+ }
+
private int clearBugreportFlag(int flags, @BugreportParams.BugreportFlag int flag) {
flags &= ~flag;
return flags;
@@ -541,19 +618,28 @@
throw new IllegalArgumentException(message);
}
-
private final class DumpstateListener extends IDumpstateListener.Stub
implements DeathRecipient {
+
+ private static int sNextId;
+
+ private final int mId = ++sNextId; // used for debugging purposes only
private final IDumpstateListener mListener;
private final IDumpstate mDs;
- private boolean mDone = false;
private final Pair<Integer, String> mCaller;
+ private final boolean mReportFinishedFile;
+ private int mProgress; // used for debugging purposes only
+ private boolean mDone;
DumpstateListener(IDumpstateListener listener, IDumpstate ds,
- @Nullable Pair<Integer, String> caller) {
+ Pair<Integer, String> caller, boolean reportFinishedFile) {
+ if (DEBUG) {
+ Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
+ }
mListener = listener;
mDs = ds;
mCaller = caller;
+ mReportFinishedFile = reportFinishedFile;
try {
mDs.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
@@ -563,35 +649,51 @@
@Override
public void onProgress(int progress) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onProgress: %d", progress);
+ }
+ mProgress = progress;
mListener.onProgress(progress);
}
@Override
public void onError(int errorCode) throws RemoteException {
+ Slogf.e(TAG, "onError(): %d", errorCode);
synchronized (mLock) {
- mDone = true;
+ releaseItselfLocked();
+ reportFinishedLocked("ErroCode: " + errorCode);
}
mListener.onError(errorCode);
}
@Override
public void onFinished(String bugreportFile) throws RemoteException {
+ Slogf.i(TAG, "onFinished(): %s", bugreportFile);
synchronized (mLock) {
- mDone = true;
+ releaseItselfLocked();
+ reportFinishedLocked("File: " + bugreportFile);
}
- if (mCaller != null) {
+ if (mReportFinishedFile) {
mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
+ } else if (DEBUG) {
+ Slog.d(TAG, "Not reporting finished file");
}
mListener.onFinished(bugreportFile);
}
@Override
public void onScreenshotTaken(boolean success) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onScreenshotTaken(): %b", success);
+ }
mListener.onScreenshotTaken(success);
}
@Override
public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(TAG, "onUiIntensiveBugreportDumpsFinished()");
+ }
mListener.onUiIntensiveBugreportDumpsFinished();
}
@@ -617,5 +719,39 @@
}
mDs.asBinder().unlinkToDeath(this, 0);
}
+
+ @Override
+ public String toString() {
+ return "DumpstateListener[id=" + mId + ", progress=" + mProgress + "]";
+ }
+
+ @GuardedBy("mLock")
+ private void reportFinishedLocked(String message) {
+ mNumberFinishedBugreports++;
+ mFinishedBugreports.log("Caller: " + callerToString(mCaller) + " " + message);
+ }
+
+ private void dump(PrintWriter pw) {
+ pw.println("DumpstateListener:");
+ pw.printf(" id: %d\n", mId);
+ pw.printf(" caller: %s\n", callerToString(mCaller));
+ pw.printf(" reports finished file: %b\n", mReportFinishedFile);
+ pw.printf(" progress: %d\n", mProgress);
+ pw.printf(" done: %b\n", mDone);
+ }
+
+ @GuardedBy("mLock")
+ private void releaseItselfLocked() {
+ mDone = true;
+ if (mCurrentDumpstateListener == this) {
+ if (DEBUG) {
+ Slogf.d(TAG, "releaseItselfLocked(): releasing %s", this);
+ }
+ mCurrentDumpstateListener = null;
+ } else {
+ Slogf.w(TAG, "releaseItselfLocked(): " + this + " is finished, but current listener"
+ + " is " + mCurrentDumpstateListener);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index b4b8cb2..ad77ef7 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -28,6 +28,7 @@
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Intent;
@@ -620,12 +621,15 @@
extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList);
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidList);
final int flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND;
+ final Bundle options = new BroadcastOptions()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .toBundle();
handler.post(() -> mBroadcastHelper.sendPackageBroadcast(intent, null /* pkg */,
extras, flags, null /* targetPkg */, null /* finishedReceiver */,
new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */,
(callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList(
mPm.snapshotComputer(), callingUid, intentExtras),
- null /* bOptions */));
+ options));
}
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a36e9f9..927a722 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1276,7 +1276,15 @@
getDevicePolicyManagerInternal().broadcastIntentToManifestReceivers(
intent, parentHandle, /* requiresPermission= */ true);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
- mContext.sendBroadcastAsUser(intent, parentHandle);
+ final Bundle options = new BroadcastOptions()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ // Both actions use single namespace because only the final state matters.
+ .setDeliveryGroupMatchingKey(
+ Intent.ACTION_MANAGED_PROFILE_AVAILABLE /* namespace */,
+ String.valueOf(profileHandle.getIdentifier()) /* key */)
+ .toBundle();
+ mContext.sendBroadcastAsUser(intent, parentHandle, /* receiverPermission= */ null, options);
}
@Override
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 88d64df..35e88c1 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -33,6 +33,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityThread;
@@ -178,6 +179,7 @@
private final SessionMonitor mSessionMonitor;
private int mCurrentUserId;
private boolean mTracingEnabled;
+ private int mLastSystemKey = -1;
private final TileRequestTracker mTileRequestTracker;
@@ -905,6 +907,8 @@
return;
}
+ mLastSystemKey = key;
+
if (mBar != null) {
try {
mBar.handleSystemKey(key);
@@ -914,6 +918,14 @@
}
@Override
+ @TestApi
+ public int getLastSystemKey() {
+ enforceStatusBar();
+
+ return mLastSystemKey;
+ }
+
+ @Override
public void showPinningEnterExitToast(boolean entering) throws RemoteException {
if (mBar != null) {
try {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 12be1d3..d4f151f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -104,6 +104,7 @@
import android.app.WaitResult;
import android.app.WindowConfiguration;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
import android.compat.annotation.EnabledSince;
import android.content.IIntentSender;
import android.content.Intent;
@@ -188,7 +189,7 @@
* Feature flag for go/activity-security rules
*/
@ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Disabled
static final long ASM_RESTRICTIONS = 230590090L;
private final ActivityTaskManagerService mService;
@@ -2947,8 +2948,6 @@
if (differentTopTask && !mAvoidMoveToFront) {
mStartActivity.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- // TODO(b/264487981): Consider using BackgroundActivityStartController to determine
- // whether to bring the launching activity to the front.
if (mSourceRecord == null || inTopNonFinishingTask(mSourceRecord)) {
// We really do want to push this one into the user's face, right now.
if (mLaunchTaskBehind && mSourceRecord != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 555cd38..7b9cc6f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -75,9 +75,7 @@
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
-import static com.android.server.am.ActivityManagerService.dumpStackTraces;
import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONFIG_WILL_CHANGE;
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.CONTROLLER;
@@ -95,6 +93,8 @@
import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
import static com.android.server.am.EventLogTags.writeConfigurationChanged;
+import static com.android.server.am.StackTracesDumpHelper.ANR_TRACE_DIR;
+import static com.android.server.am.StackTracesDumpHelper.dumpStackTraces;
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index bbe7a33..90ec964 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -34,7 +34,7 @@
import android.view.InputApplicationHandle;
import com.android.internal.os.TimeoutRecord;
-import com.android.server.am.ActivityManagerService;
+import com.android.server.am.StackTracesDumpHelper;
import com.android.server.criticalevents.CriticalEventLog;
import java.io.File;
@@ -336,7 +336,7 @@
String criticalEvents =
CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
- final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+ final File tracesFile = StackTracesDumpHelper.dumpStackTraces(firstPids,
null /* processCpuTracker */, null /* lastPids */,
CompletableFuture.completedFuture(nativePids),
null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index b67bc62..fb79c067 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -262,14 +262,23 @@
if (!isOccluded || prevActivity.canShowWhenLocked()) {
// We have another Activity in the same currentTask to go to
final WindowContainer parent = currentActivity.getParent();
- final boolean isCustomize = parent != null
+ final boolean canCustomize = parent != null
&& (parent.asTask() != null
|| (parent.asTaskFragment() != null
- && parent.canCustomizeAppTransition()))
- && isCustomizeExitAnimation(window);
- if (isCustomize) {
- infoBuilder.setWindowAnimations(
- window.mAttrs.packageName, window.mAttrs.windowAnimations);
+ && parent.canCustomizeAppTransition()));
+ if (canCustomize) {
+ if (isCustomizeExitAnimation(window)) {
+ infoBuilder.setWindowAnimations(
+ window.mAttrs.packageName, window.mAttrs.windowAnimations);
+ }
+ final ActivityRecord.CustomAppTransition customAppTransition =
+ currentActivity.getCustomAnimation(false/* open */);
+ if (customAppTransition != null) {
+ infoBuilder.setCustomAnimation(currentActivity.packageName,
+ customAppTransition.mExitAnim,
+ customAppTransition.mEnterAnim,
+ customAppTransition.mBackgroundColor);
+ }
}
removedWindowContainer = currentActivity;
prevTask = prevActivity.getTask();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 37ee2e2..1604c2a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1567,7 +1567,14 @@
if (configChanged) {
mWaitingForConfig = true;
if (mTransitionController.isShellTransitionsEnabled()) {
- requestChangeTransitionIfNeeded(changes, null /* displayChange */);
+ final TransitionRequestInfo.DisplayChange change =
+ mTransitionController.isCollecting()
+ ? null : new TransitionRequestInfo.DisplayChange(mDisplayId);
+ if (change != null) {
+ change.setStartAbsBounds(currentDisplayConfig.windowConfiguration.getBounds());
+ change.setEndAbsBounds(mTmpConfiguration.windowConfiguration.getBounds());
+ }
+ requestChangeTransitionIfNeeded(changes, change);
} else if (mLastHasContent) {
mWmService.startFreezingDisplay(0 /* exitAnim */, 0 /* enterAnim */, this);
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 868a15d..a8c9cd3 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -334,7 +334,11 @@
// remove caption insets from floating windows.
// TODO(b/254128050): Remove this workaround after we find a way to update window frames
// and caption insets frames simultaneously.
- state.removeSource(InsetsState.ITYPE_CAPTION_BAR);
+ for (int i = state.sourceSize() - 1; i >= 0; i--) {
+ if (state.sourceAt(i).getType() == Type.captionBar()) {
+ state.removeSourceAt(i);
+ }
+ }
}
final SparseArray<WindowContainerInsetsSourceProvider> providers =
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index f314b21..7e267e4 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -30,6 +30,7 @@
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.WindowConfiguration;
+import android.graphics.Rect;
import android.os.Handler;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -465,6 +466,28 @@
return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
}
+ /** Whether the display change should run with blast sync. */
+ private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
+ if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+ // 180 degrees rotation change may not change screen size. So the clients may draw
+ // some frames before and after the display projection transaction is applied by the
+ // remote player. That may cause some buffers to show in different rotation. So use
+ // sync method to pause clients drawing until the projection transaction is applied.
+ return true;
+ }
+ final Rect startBounds = displayChange.getStartAbsBounds();
+ final Rect endBounds = displayChange.getEndAbsBounds();
+ if (startBounds == null || endBounds == null) return false;
+ final int startWidth = startBounds.width();
+ final int startHeight = startBounds.height();
+ final int endWidth = endBounds.width();
+ final int endHeight = endBounds.height();
+ // This is changing screen resolution. Because the screen decor layers are excluded from
+ // screenshot, their draw transactions need to run with the start transaction.
+ return (endWidth > startWidth) == (endHeight > startHeight)
+ && (endWidth != startWidth || endHeight != startHeight);
+ }
+
/**
* If a transition isn't requested yet, creates one and asks the TransitionPlayer (Shell) to
* start it. Collection can start immediately.
@@ -494,12 +517,7 @@
} else {
newTransition = requestStartTransition(createTransition(type, flags),
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
- if (newTransition != null && displayChange != null && (displayChange.getStartRotation()
- + displayChange.getEndRotation()) % 2 == 0) {
- // 180 degrees rotation change may not change screen size. So the clients may draw
- // some frames before and after the display projection transaction is applied by the
- // remote player. That may cause some buffers to show in different rotation. So use
- // sync method to pause clients drawing until the projection transaction is applied.
+ if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
BLASTSyncEngine.METHOD_BLAST);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d1bd06f..232b817 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2343,6 +2343,9 @@
// to be removed before the parent (so that the sync-engine tracking works). Since
// WindowStateAnimator is a "virtual" child, we have to do it manually here.
mWinAnimator.destroySurfaceLocked(getSyncTransaction());
+ if (!mDrawHandlers.isEmpty()) {
+ mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
+ }
super.removeImmediately();
final DisplayContent dc = getDisplayContent();
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a5b1943..075dcd5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -137,7 +137,6 @@
jmethodID notifyDropWindow;
jmethodID getParentSurfaceForPointers;
jmethodID isPerDisplayTouchModeEnabled;
- jmethodID isStylusPointerIconEnabled;
} gServiceClassInfo;
static struct {
@@ -309,6 +308,7 @@
std::optional<std::string> getBluetoothAddress(int32_t deviceId);
void setStylusButtonMotionEventsEnabled(bool enabled);
FloatPoint getMouseCursorPosition();
+ void setStylusPointerIconEnabled(bool enabled);
/* --- InputReaderPolicyInterface implementation --- */
@@ -430,6 +430,9 @@
// True to enable a zone on the right-hand side of touchpads where clicks will be turned
// into context (a.k.a. "right") clicks.
bool touchpadRightClickZoneEnabled{false};
+
+ // True if a pointer icon should be shown for stylus pointers.
+ bool stylusPointerIconEnabled{false};
} mLocked GUARDED_BY(mLock);
std::atomic<bool> mInteractive;
@@ -662,12 +665,6 @@
outConfig->pointerGestureTapSlop = hoverTapSlop;
}
- jboolean stylusPointerIconEnabled =
- env->CallBooleanMethod(mServiceObj, gServiceClassInfo.isStylusPointerIconEnabled);
- if (!checkAndClearExceptionFromCallback(env, "isStylusPointerIconEnabled")) {
- outConfig->stylusPointerIconEnabled = stylusPointerIconEnabled;
- }
-
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -692,6 +689,8 @@
outConfig->disabledDevices = mLocked.disabledInputDevices;
outConfig->stylusButtonMotionEventsEnabled = mLocked.stylusButtonMotionEventsEnabled;
+
+ outConfig->stylusPointerIconEnabled = mLocked.stylusPointerIconEnabled;
} // release lock
}
@@ -1664,6 +1663,21 @@
return pc->getPosition();
}
+void NativeInputManager::setStylusPointerIconEnabled(bool enabled) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.stylusPointerIconEnabled == enabled) {
+ return;
+ }
+
+ mLocked.stylusPointerIconEnabled = enabled;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
// ----------------------------------------------------------------------------
static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) {
@@ -2565,6 +2579,12 @@
return outArr;
}
+static void nativeSetStylusPointerIconEnabled(JNIEnv* env, jobject nativeImplObj,
+ jboolean enabled) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setStylusPointerIconEnabled(enabled);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -2659,6 +2679,7 @@
{"setStylusButtonMotionEventsEnabled", "(Z)V",
(void*)nativeSetStylusButtonMotionEventsEnabled},
{"getMouseCursorPosition", "()[F", (void*)nativeGetMouseCursorPosition},
+ {"setStylusPointerIconEnabled", "(Z)V", (void*)nativeSetStylusPointerIconEnabled},
};
#define FIND_CLASS(var, className) \
@@ -2819,9 +2840,6 @@
GET_METHOD_ID(gServiceClassInfo.isPerDisplayTouchModeEnabled, clazz,
"isPerDisplayTouchModeEnabled", "()Z");
- GET_METHOD_ID(gServiceClassInfo.isStylusPointerIconEnabled, clazz, "isStylusPointerIconEnabled",
- "()Z");
-
// InputDevice
FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index 8ccc61b..1164516 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -25,19 +25,16 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
/** Contains information on what CredentialProvider has what provisioned Credential. */
public class CredentialDescriptionRegistry {
- private static final String FLAT_STRING_SPLIT_REGEX = ";";
private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
@GuardedBy("sLock")
@@ -53,15 +50,15 @@
/** Represents the results of a given query into the registry. */
public static final class FilterResult {
final String mPackageName;
- final String mFlattenedRequest;
+ final Set<String> mElementKeys;
final List<CredentialEntry> mCredentialEntries;
@VisibleForTesting
FilterResult(String packageName,
- String flattenedRequest,
+ Set<String> elementKeys,
List<CredentialEntry> credentialEntries) {
mPackageName = packageName;
- mFlattenedRequest = flattenedRequest;
+ mElementKeys = elementKeys;
mCredentialEntries = credentialEntries;
}
}
@@ -166,18 +163,17 @@
/** Returns package names and entries of a CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
public Set<FilterResult> getFilteredResultForProvider(String packageName,
- String flatRequestString) {
+ Set<String> requestedKeyElements) {
Set<FilterResult> result = new HashSet<>();
if (!mCredentialDescriptions.containsKey(packageName)) {
return result;
}
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
- Set<String> unflattenedRequestString = flatStringToSet(flatRequestString);
for (CredentialDescription containedDescription: currentSet) {
- if (checkForMatch(flatStringToSet(containedDescription.getFlattenedRequestString()),
- unflattenedRequestString)) {
+ if (checkForMatch(containedDescription.getSupportedElementKeys(),
+ requestedKeyElements)) {
result.add(new FilterResult(packageName,
- containedDescription.getFlattenedRequestString(), containedDescription
+ containedDescription.getSupportedElementKeys(), containedDescription
.getCredentialEntries()));
}
}
@@ -186,18 +182,15 @@
/** Returns package names of CredentialProviders that can satisfy a given
* {@link CredentialDescription}. */
- public Set<FilterResult> getMatchingProviders(Set<String> flatRequestStrings) {
+ public Set<FilterResult> getMatchingProviders(Set<Set<String>> supportedElementKeys) {
Set<FilterResult> result = new HashSet<>();
- Set<Set<String>> unflattenedRequestStrings = flatRequestStrings.stream().map(
- CredentialDescriptionRegistry::flatStringToSet).collect(Collectors.toSet());
for (String packageName: mCredentialDescriptions.keySet()) {
Set<CredentialDescription> currentSet = mCredentialDescriptions.get(packageName);
for (CredentialDescription containedDescription : currentSet) {
- if (canProviderSatisfyAny(flatStringToSet(containedDescription
- .getFlattenedRequestString()),
- unflattenedRequestStrings)) {
+ if (canProviderSatisfyAny(containedDescription.getSupportedElementKeys(),
+ supportedElementKeys)) {
result.add(new FilterResult(packageName,
- containedDescription.getFlattenedRequestString(), containedDescription
+ containedDescription.getSupportedElementKeys(), containedDescription
.getCredentialEntries()));
}
}
@@ -211,24 +204,19 @@
}
}
- private static boolean canProviderSatisfyAny(Set<String> registeredUnflattenedStrings,
- Set<Set<String>> requestedUnflattenedStrings) {
- for (Set<String> requestedUnflattenedString : requestedUnflattenedStrings) {
- if (registeredUnflattenedStrings.containsAll(requestedUnflattenedString)) {
+ private static boolean canProviderSatisfyAny(Set<String> registeredElementKeys,
+ Set<Set<String>> requestedElementKeys) {
+ for (Set<String> requestedUnflattenedString : requestedElementKeys) {
+ if (registeredElementKeys.containsAll(requestedUnflattenedString)) {
return true;
}
}
return false;
}
- static boolean checkForMatch(Set<String> registeredUnflattenedStrings,
- Set<String> requestedUnflattenedString) {
- return registeredUnflattenedStrings.containsAll(requestedUnflattenedString);
- }
-
- static Set<String> flatStringToSet(String flatString) {
- return new HashSet<>(Arrays
- .asList(flatString.split(FLAT_STRING_SPLIT_REGEX)));
+ static boolean checkForMatch(Set<String> registeredElementKeys,
+ Set<String> requestedElementKeys) {
+ return registeredElementKeys.containsAll(requestedElementKeys);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 90b92f4..06da76e5 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -339,13 +339,14 @@
CredentialDescriptionRegistry.forUser(UserHandle.getCallingUserId());
// All requested credential descriptions based on the given request.
- Set<String> requestedCredentialDescriptions =
+ Set<Set<String>> requestedCredentialDescriptions =
options.stream()
.map(
getCredentialOption ->
- getCredentialOption
+ new HashSet<>(getCredentialOption
.getCredentialRetrievalData()
- .getString(CredentialOption.FLATTENED_REQUEST))
+ .getStringArrayList(
+ CredentialOption.SUPPORTED_ELEMENT_KEYS)))
.collect(Collectors.toSet());
// All requested credential descriptions based on the given request.
@@ -356,15 +357,13 @@
new HashSet<>();
for (CredentialDescriptionRegistry.FilterResult filterResult : filterResults) {
- Set<String> registeredUnflattenedStrings = CredentialDescriptionRegistry
- .flatStringToSet(filterResult.mFlattenedRequest);
for (CredentialOption credentialOption : options) {
- Set<String> requestedUnflattenedStrings = CredentialDescriptionRegistry
- .flatStringToSet(credentialOption
+ Set<String> requestedElementKeys = new HashSet<>(
+ credentialOption
.getCredentialRetrievalData()
- .getString(CredentialOption.FLATTENED_REQUEST));
- if (CredentialDescriptionRegistry.checkForMatch(registeredUnflattenedStrings,
- requestedUnflattenedStrings)) {
+ .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
+ if (CredentialDescriptionRegistry.checkForMatch(filterResult.mElementKeys,
+ requestedElementKeys)) {
result.add(new Pair<>(credentialOption, filterResult));
}
}
@@ -511,28 +510,20 @@
if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(
- getCredentialOption ->
- !TextUtils.isEmpty(
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST,
- null)))
+ .filter(credentialOption -> credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS) != null)
.toList();
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(
- getCredentialOption ->
- TextUtils.isEmpty(
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST,
- null)))
+ .filter(credentialOption -> credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS) == null)
.toList();
List<ProviderSession> sessionsWithoutRemoteService =
@@ -590,28 +581,20 @@
if (isCredentialDescriptionApiEnabled()) {
List<CredentialOption> optionsThatRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(
- getCredentialOption ->
- !TextUtils.isEmpty(
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST,
- null)))
+ .filter(credentialOption -> credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS) != null)
.toList();
List<CredentialOption> optionsThatDoNotRequireActiveCredentials =
request.getCredentialOptions().stream()
- .filter(
- getCredentialOption ->
- TextUtils.isEmpty(
- getCredentialOption
- .getCredentialRetrievalData()
- .getString(
- CredentialOption
- .FLATTENED_REQUEST,
- null)))
+ .filter(credentialOption -> credentialOption
+ .getCredentialRetrievalData()
+ .getStringArrayList(
+ CredentialOption
+ .SUPPORTED_ELEMENT_KEYS) == null)
.toList();
List<ProviderSession> sessionsWithoutRemoteService =
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 85c78445..8b14757b 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -38,6 +38,7 @@
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -103,7 +104,7 @@
@NonNull
private final String mCredentialProviderPackageName;
@NonNull
- private final String mFlattenedRequestOptionString;
+ private final Set<String> mElementKeys;
@VisibleForTesting
List<CredentialEntry> mCredentialEntries;
@@ -119,9 +120,9 @@
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
mCredentialProviderPackageName = servicePackageName;
- mFlattenedRequestOptionString = requestOption
+ mElementKeys = new HashSet<>(requestOption
.getCredentialRetrievalData()
- .getString(CredentialOption.FLATTENED_REQUEST);
+ .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
}
protected ProviderRegistryGetSession(@NonNull Context context,
@@ -136,9 +137,9 @@
mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
mCallingAppInfo = callingAppInfo;
mCredentialProviderPackageName = servicePackageName;
- mFlattenedRequestOptionString = requestOption
+ mElementKeys = new HashSet<>(requestOption
.getCredentialRetrievalData()
- .getString(CredentialOption.FLATTENED_REQUEST);
+ .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS));
}
private List<Entry> prepareUiCredentialEntries(
@@ -257,7 +258,7 @@
protected void invokeSession() {
mProviderResponse = mCredentialDescriptionRegistry
.getFilteredResultForProvider(mCredentialProviderPackageName,
- mFlattenedRequestOptionString);
+ mElementKeys);
mCredentialEntries = mProviderResponse.stream().flatMap(
(Function<CredentialDescriptionRegistry.FilterResult,
Stream<CredentialEntry>>) filterResult
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
index a1b9b98..2a256f2 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java
@@ -80,7 +80,7 @@
final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
assertThat(state).isNotNull();
- assertThat(state.hasEdiorFocused()).isTrue();
+ assertThat(state.hasEditorFocused()).isTrue();
assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
assertThat(state.isRequestedImeVisible()).isTrue();
@@ -95,7 +95,7 @@
final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
assertThat(state).isNotNull();
- assertThat(state.hasEdiorFocused()).isTrue();
+ assertThat(state.hasEditorFocused()).isTrue();
assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
assertThat(state.isRequestedImeVisible()).isTrue();
@@ -113,7 +113,7 @@
final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
assertThat(state).isNotNull();
- assertThat(state.hasEdiorFocused()).isTrue();
+ assertThat(state.hasEditorFocused()).isTrue();
assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
assertThat(state.isRequestedImeVisible()).isFalse();
@@ -131,7 +131,7 @@
final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
assertThat(state).isNotNull();
- assertThat(state.hasEdiorFocused()).isTrue();
+ assertThat(state.hasEditorFocused()).isTrue();
assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
assertThat(state.isRequestedImeVisible()).isFalse();
@@ -149,7 +149,7 @@
final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken);
assertThat(state).isNotNull();
- assertThat(state.hasEdiorFocused()).isTrue();
+ assertThat(state.hasEditorFocused()).isTrue();
assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED);
assertThat(state.isRequestedImeVisible()).isFalse();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 0ab984b..fc503b7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -66,7 +66,6 @@
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
-import com.android.server.display.brightness.BrightnessEvent;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.display.layout.Layout;
import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
@@ -565,8 +564,8 @@
.thenReturn(brightness);
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
@@ -603,8 +602,8 @@
.thenReturn(brightness);
dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
- any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness())
+ .thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
index 4d11296..a1937ce 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/LocationManagerServiceTest.java
@@ -53,6 +53,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -69,6 +70,7 @@
private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
private static final String CALLER_PACKAGE = "caller_package";
private static final String MISSING_PERMISSION = "missing_permission";
+ private static final String ATTRIBUTION_TAG = "test_tag";
private TestInjector mInjector;
private LocationManagerService mLocationManagerService;
@@ -136,6 +138,7 @@
}
@Test
+ @Ignore("b/274432939") // Test is flaky for as of yet unknown reasons
public void testRequestLocationUpdates() {
LocationRequest request = new LocationRequest.Builder(0).build();
mLocationManagerService.registerLocationListener(
@@ -143,7 +146,7 @@
request,
mLocationListener,
CALLER_PACKAGE,
- /* attributionTag= */ null,
+ ATTRIBUTION_TAG,
"any_listener_id");
verify(mProviderWithPermission).onSetRequestPublic(any());
}
@@ -159,7 +162,7 @@
request,
mLocationListener,
CALLER_PACKAGE,
- /* attributionTag= */ null,
+ ATTRIBUTION_TAG,
"any_listener_id"));
}
diff --git a/services/tests/servicestests/res/xml/keyboard_layouts.xml b/services/tests/servicestests/res/xml/keyboard_layouts.xml
index b5a05fc..5f3fcd6 100644
--- a/services/tests/servicestests/res/xml/keyboard_layouts.xml
+++ b/services/tests/servicestests/res/xml/keyboard_layouts.xml
@@ -73,9 +73,17 @@
android:keyboardLocale="ru-Cyrl" />
<keyboard-layout
+ android:name="keyboard_layout_english_without_script_code"
+ android:label="English(No script code)"
+ android:keyboardLayout="@raw/dummy_keyboard_layout"
+ android:keyboardLocale="en"
+ android:keyboardLayoutType="qwerty" />
+
+ <keyboard-layout
android:name="keyboard_layout_vendorId:1,productId:1"
android:label="vendorId:1,productId:1"
android:keyboardLayout="@raw/dummy_keyboard_layout"
androidprv:vendorId="1"
androidprv:productId="1" />
+
</keyboard-layouts>
diff --git a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
index 169210f..ab2749e 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/CredentialDescriptionRegistryTest.java
@@ -33,6 +33,7 @@
import org.junit.runner.RunWith;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -52,10 +53,12 @@
private static final String CALLING_PACKAGE_NAME_2 = "com.credman.app2";
private static final String MDOC_CREDENTIAL_TYPE = "MDOC";
private static final String PASSKEY_CREDENTIAL_TYPE = "PASSKEY";
- private static final String FLATTENED_REGISTRY =
- "FLATTENED_REQ;FLATTENED_REQ123;FLATTENED_REQa";
- private static final String FLATTENED_REGISTRY_2 = "FLATTENED_REQ_2";
- private static final String FLATTENED_REQUEST = "FLATTENED_REQ;FLATTENED_REQ123";
+ private static final HashSet<String> FLATTENED_REGISTRY = new HashSet<>(List.of(
+ "FLATTENED_REQ", "FLATTENED_REQ123", "FLATTENED_REQa"));
+ private static final HashSet<String> FLATTENED_REGISTRY_2 =
+ new HashSet<>(List.of("FLATTENED_REQ_2"));
+ private static final HashSet<String> FLATTENED_REQUEST =
+ new HashSet<>(List.of("FLATTENED_REQ;FLATTENED_REQ123"));
private CredentialDescriptionRegistry mCredentialDescriptionRegistry;
private CredentialEntry mEntry;
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
index 4c8e70a..13d49aa 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -59,6 +60,7 @@
import org.mockito.MockitoAnnotations;
import java.security.cert.CertificateException;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -74,7 +76,8 @@
private static final String CALLING_PACKAGE_NAME = "com.credman.app";
private static final int USER_ID_1 = 1;
- private static final String FLATTENED_REQUEST = "FLATTENED_REQ";
+ private static final ArrayList<String> FLATTENED_REQUEST =
+ new ArrayList<>(List.of("FLATTENED_REQ"));
private static final String CP_SERVICE_NAME = "CredentialProvider";
private static final ComponentName CREDENTIAL_PROVIDER_COMPONENT =
new ComponentName(CALLING_PACKAGE_NAME, CP_SERVICE_NAME);
@@ -102,7 +105,8 @@
MockitoAnnotations.initMocks(this);
final Context context = ApplicationProvider.getApplicationContext();
mRetrievalData = new Bundle();
- mRetrievalData.putString(CredentialOption.FLATTENED_REQUEST, FLATTENED_REQUEST);
+ mRetrievalData.putStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS,
+ FLATTENED_REQUEST);
mCallingAppInfo = createCallingAppInfo();
mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData,
new Bundle(), false);
@@ -114,10 +118,10 @@
when(mEntry.getSlice()).thenReturn(mSlice);
when(mEntry2.getSlice()).thenReturn(mSlice2);
mResult = new CredentialDescriptionRegistry.FilterResult(CALLING_PACKAGE_NAME,
- FLATTENED_REQUEST,
+ new HashSet<>(FLATTENED_REQUEST),
List.of(mEntry, mEntry2));
mResponse.add(mResult);
- when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anyString()))
+ when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anySet()))
.thenReturn(mResponse);
mProviderRegistryGetSession = ProviderRegistryGetSession
.createNewSession(context, USER_ID_1, mGetRequestSession,
@@ -129,7 +133,8 @@
@Test
public void testInvokeSession_existingProvider_setsResults() {
final ArgumentCaptor<String> packageNameCaptor = ArgumentCaptor.forClass(String.class);
- final ArgumentCaptor<String> flattenedRequestCaptor = ArgumentCaptor.forClass(String.class);
+ final ArgumentCaptor<Set<String>> flattenedRequestCaptor =
+ ArgumentCaptor.forClass(Set.class);
final ArgumentCaptor<ProviderSession.Status> statusCaptor =
ArgumentCaptor.forClass(ProviderSession.Status.class);
final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
@@ -141,7 +146,7 @@
packageNameCaptor.capture(),
flattenedRequestCaptor.capture());
assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME);
- assertThat(flattenedRequestCaptor.getValue()).isEqualTo(FLATTENED_REQUEST);
+ assertThat(flattenedRequestCaptor.getValue()).containsExactly(FLATTENED_REQUEST);
verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
cpComponentNameCaptor.capture());
assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
new file mode 100644
index 0000000..eb208d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.display.brightness.strategy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.hardware.display.BrightnessConfiguration;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.view.Display;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
+import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AutomaticBrightnessStrategyTest {
+ private static final int DISPLAY_ID = 0;
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
+ @Mock
+ private AutomaticBrightnessController mAutomaticBrightnessController;
+
+ private BrightnessConfiguration mBrightnessConfiguration;
+ private float mDefaultScreenAutoBrightnessAdjustment;
+ private Context mContext;
+ private AutomaticBrightnessStrategy mAutomaticBrightnessStrategy;
+
+ @Before
+ public void before() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
+ mDefaultScreenAutoBrightnessAdjustment = Settings.System.getFloat(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN);
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f);
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID);
+
+ mBrightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ when(mAutomaticBrightnessController.hasUserDataPoints()).thenReturn(true);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ mAutomaticBrightnessController);
+ mAutomaticBrightnessStrategy.setBrightnessConfiguration(mBrightnessConfiguration,
+ true);
+ }
+
+ @After
+ public void after() {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, mDefaultScreenAutoBrightnessAdjustment);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenDisabled() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(false);
+ int targetDisplayState = Display.STATE_ON;
+ boolean allowAutoBrightnessWhileDozing = false;
+ float brightnessState = Float.NaN;
+ int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(true);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, 0.5f,
+ false, policy, true);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenEnabledAndDisplayIsDozing() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_DOZE;
+ float brightnessState = Float.NaN;
+ boolean allowAutoBrightnessWhileDozing = true;
+ int brightnessReason = BrightnessReason.REASON_DOZE;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.4f);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, 0.4f,
+ true, policy, true);
+ }
+
+ @Test
+ public void setAutoBrightnessWhenEnabledAndDisplayIsOn() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ int targetDisplayState = Display.STATE_ON;
+ float brightnessState = Float.NaN;
+ boolean allowAutoBrightnessWhileDozing = false;
+ int brightnessReason = BrightnessReason.REASON_OVERRIDE;
+ float lastUserSetBrightness = 0.2f;
+ boolean userSetBrightnessChanged = true;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ float pendingBrightnessAdjustment = 0.1f;
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
+ allowAutoBrightnessWhileDozing, brightnessState, brightnessReason, policy,
+ lastUserSetBrightness, userSetBrightnessChanged);
+ verify(mAutomaticBrightnessController)
+ .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED,
+ mBrightnessConfiguration,
+ lastUserSetBrightness,
+ userSetBrightnessChanged, pendingBrightnessAdjustment,
+ true, policy, true);
+ }
+
+ @Test
+ public void accommodateUserBrightnessChangesWorksAsExpected() {
+ // Verify the state if automaticBrightnessController is configured.
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ boolean userSetBrightnessChanged = true;
+ float lastUserSetScreenBrightness = 0.2f;
+ int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+ BrightnessConfiguration brightnessConfiguration = new BrightnessConfiguration.Builder(
+ new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build();
+ int autoBrightnessState = AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
+ float temporaryAutoBrightnessAdjustments = 0.4f;
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ setTemporaryAutoBrightnessAdjustment(temporaryAutoBrightnessAdjustments);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, brightnessConfiguration,
+ autoBrightnessState);
+ verify(mAutomaticBrightnessController).configure(autoBrightnessState,
+ brightnessConfiguration,
+ lastUserSetScreenBrightness,
+ userSetBrightnessChanged, temporaryAutoBrightnessAdjustments,
+ /* userChangedAutoBrightnessAdjustment= */ false, policy,
+ /* shouldResetShortTermModel= */ true);
+ assertTrue(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertFalse(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertTrue(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ // Verify the state when automaticBrightnessController is not configured
+ setTemporaryAutoBrightnessAdjustment(Float.NaN);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(null);
+ mAutomaticBrightnessStrategy.setShouldResetShortTermModel(true);
+ mAutomaticBrightnessStrategy.accommodateUserBrightnessChanges(userSetBrightnessChanged,
+ lastUserSetScreenBrightness, policy, brightnessConfiguration,
+ autoBrightnessState);
+ assertFalse(mAutomaticBrightnessStrategy.isTemporaryAutoBrightnessAdjustmentApplied());
+ assertTrue(mAutomaticBrightnessStrategy.shouldResetShortTermModel());
+ assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive());
+ }
+
+ @Test
+ public void adjustAutomaticBrightnessStateIfValid() throws Settings.SettingNotFoundException {
+ float brightnessState = 0.3f;
+ float autoBrightnessAdjustment = 0.2f;
+ when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn(
+ autoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.adjustAutomaticBrightnessStateIfValid(brightnessState);
+ assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(autoBrightnessAdjustment, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ assertEquals(BrightnessReason.ADJUSTMENT_AUTO,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ float invalidBrightness = -0.5f;
+ mAutomaticBrightnessStrategy
+ .adjustAutomaticBrightnessStateIfValid(invalidBrightness);
+ assertFalse(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ assertEquals(autoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(0,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentReasonsFlags());
+ }
+
+ @Test
+ public void updatePendingAutoBrightnessAdjustments() {
+ // Verify the state when the pendingAutoBrightnessAdjustments are not present
+ setPendingAutoBrightnessAdjustment(Float.NaN);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, but
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are the same
+ float autoBrightnessAdjustment = 0.3f;
+ setPendingAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ setAutoBrightnessAdjustment(autoBrightnessAdjustment);
+ assertFalse(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertFalse(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ // Verify the state when the pendingAutoBrightnessAdjustments are present, and
+ // pendingAutoBrightnessAdjustments and autoBrightnessAdjustments are not the same
+ float pendingAutoBrightnessAdjustment = 0.2f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustment);
+ setTemporaryAutoBrightnessAdjustment(0.1f);
+ assertTrue(mAutomaticBrightnessStrategy.processPendingAutoBrightnessAdjustments());
+ assertTrue(mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged());
+ assertEquals(pendingAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(),
+ 0.0f);
+ assertEquals(Float.NaN, mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ @Test
+ public void setAutomaticBrightnessWorksAsExpected() {
+ float automaticScreenBrightness = 0.3f;
+ AutomaticBrightnessController automaticBrightnessController = mock(
+ AutomaticBrightnessController.class);
+ when(automaticBrightnessController.getAutomaticScreenBrightness()).thenReturn(
+ automaticScreenBrightness);
+ mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+ automaticBrightnessController);
+ assertEquals(automaticScreenBrightness,
+ mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(), 0.0f);
+ }
+
+ @Test
+ public void shouldUseAutoBrightness() {
+ mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
+ assertTrue(mAutomaticBrightnessStrategy.shouldUseAutoBrightness());
+ }
+
+ @Test
+ public void setPendingAutoBrightnessAdjustments() throws Settings.SettingNotFoundException {
+ float pendingAutoBrightnessAdjustments = 0.3f;
+ setPendingAutoBrightnessAdjustment(pendingAutoBrightnessAdjustments);
+ assertEquals(pendingAutoBrightnessAdjustments,
+ mAutomaticBrightnessStrategy.getPendingAutoBrightnessAdjustment(), 0.0f);
+ assertEquals(pendingAutoBrightnessAdjustments, Settings.System.getFloatForUser(
+ mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
+ UserHandle.USER_CURRENT), 0.0f);
+ }
+
+ @Test
+ public void setTemporaryAutoBrightnessAdjustment() {
+ float temporaryAutoBrightnessAdjustment = 0.3f;
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ assertEquals(temporaryAutoBrightnessAdjustment,
+ mAutomaticBrightnessStrategy.getTemporaryAutoBrightnessAdjustment(), 0.0f);
+ }
+
+ @Test
+ public void setAutoBrightnessApplied() {
+ mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+ assertTrue(mAutomaticBrightnessStrategy.hasAppliedAutoBrightness());
+ }
+
+ @Test
+ public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() {
+ int newDisplayId = 1;
+ mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId);
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f);
+ assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(),
+ 0.0f);
+ }
+
+ private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
+ Settings.System.putFloat(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
+ mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(false);
+ }
+
+ private void setTemporaryAutoBrightnessAdjustment(float temporaryAutoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.setTemporaryAutoBrightnessAdjustment(
+ temporaryAutoBrightnessAdjustment);
+ }
+
+ private void setAutoBrightnessAdjustment(float autoBrightnessAdjustment) {
+ mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(autoBrightnessAdjustment);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index b660926..7729fa2 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -26,7 +26,6 @@
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.KeyboardLayout
-import android.icu.lang.UScript
import android.icu.util.ULocale
import android.os.Bundle
import android.os.test.TestLooper
@@ -52,7 +51,6 @@
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
-import java.util.Locale
private fun createKeyboard(
deviceId: Int,
@@ -553,24 +551,17 @@
0,
keyboardLayouts.size
)
-
- val englishScripts = UScript.getCode(Locale.forLanguageTag("hi-Latn"))
- for (kl in keyboardLayouts) {
- var isCompatible = false
- for (i in 0 until kl.locales.size()) {
- val locale: Locale = kl.locales.get(i) ?: continue
- val scripts = UScript.getCode(locale)
- if (scripts != null && areScriptsCompatible(scripts, englishScripts)) {
- isCompatible = true
- break
- }
- }
- assertTrue(
- "New UI: getKeyboardLayoutListForInputDevice API should only return " +
- "compatible layouts but found " + kl.descriptor,
- isCompatible
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for hi-Latn",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for hi-Latn",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
)
- }
+ )
// Check Layouts for "hi" which by default uses 'Deva' script.
keyboardLayouts =
@@ -600,6 +591,46 @@
1,
keyboardLayouts.size
)
+
+ // Special case Japanese: UScript ignores provided script code for certain language tags
+ // Should manually match provided script codes and then rely on Uscript to derive
+ // script from language tags and match those.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("ja-Latn-JP")
+ )
+ assertNotEquals(
+ "New UI: getKeyboardLayoutListForInputDevice API should return the list of " +
+ "supported layouts with matching script code for ja-Latn-JP",
+ 0,
+ keyboardLayouts.size
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(US) layout for ja-Latn-JP",
+ containsLayout(keyboardLayouts, ENGLISH_US_LAYOUT_DESCRIPTOR)
+ )
+ assertTrue("New UI: getKeyboardLayoutListForInputDevice API should return a list " +
+ "containing English(No script code) layout for ja-Latn-JP",
+ containsLayout(
+ keyboardLayouts,
+ createLayoutDescriptor("keyboard_layout_english_without_script_code")
+ )
+ )
+
+ // If script code not explicitly provided for Japanese should rely on Uscript to find
+ // derived script code and hence no suitable layout will be found.
+ keyboardLayouts =
+ keyboardLayoutManager.getKeyboardLayoutListForInputDevice(
+ keyboardDevice.identifier, USER_ID, imeInfo,
+ createImeSubtypeForLanguageTag("ja-JP")
+ )
+ assertEquals(
+ "New UI: getKeyboardLayoutListForInputDevice API should return empty list of " +
+ "supported layouts with matching script code for ja-JP",
+ 0,
+ keyboardLayouts.size
+ )
}
}
@@ -779,10 +810,10 @@
private fun createLayoutDescriptor(keyboardName: String): String =
"$PACKAGE_NAME/$RECEIVER_NAME/$keyboardName"
- private fun areScriptsCompatible(scriptList1: IntArray, scriptList2: IntArray): Boolean {
- for (s1 in scriptList1) {
- for (s2 in scriptList2) {
- if (s1 == s2) return true
+ private fun containsLayout(layoutList: Array<KeyboardLayout>, layoutDesc: String): Boolean {
+ for (kl in layoutList) {
+ if (kl.descriptor.equals(layoutDesc)) {
+ return true
}
}
return false
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index be5a6d5..9ca8d84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -74,6 +74,7 @@
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
@@ -82,6 +83,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -120,6 +122,7 @@
import android.Manifest;
import android.annotation.SuppressLint;
+import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
@@ -277,12 +280,16 @@
@RunWithLooper
public class NotificationManagerServiceTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
+ private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
private static final String PKG_NO_CHANNELS = "com.example.no.channels";
private static final int TEST_TASK_ID = 1;
private static final int UID_HEADLESS = 1_000_000;
private static final int TOAST_DURATION = 2_000;
+ private static final int SECONDARY_DISPLAY_ID = 42;
private final int mUid = Binder.getCallingUid();
+ private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
+
private TestableNotificationManagerService mService;
private INotificationManager mBinderService;
private NotificationManagerInternal mInternalService;
@@ -513,7 +520,7 @@
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"),
- UserHandle.getUserId(mUid), true, null, 0, 123);
+ mUserId, true, null, 0, 123);
ComponentName defaultComponent = ComponentName.unflattenFromString("config/device");
ArraySet<ComponentName> components = new ArraySet<>();
components.add(defaultComponent);
@@ -604,6 +611,7 @@
when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
anyString(), anyInt(), any())).thenReturn(true);
when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
+ mockIsVisibleBackgroundUsersSupported(false);
// Set the testable bubble extractor
RankingHelper rankingHelper = mService.getRankingHelper();
@@ -1039,7 +1047,7 @@
new ParceledListSlice(Arrays.asList(channel)));
verify(mWorkerHandler).post(eq(new NotificationManagerService
.ShowNotificationPermissionPromptRunnable(PKG_NO_CHANNELS,
- UserHandle.getUserId(mUid), TEST_TASK_ID, mPermissionPolicyInternal)));
+ mUserId, TEST_TASK_ID, mPermissionPolicyInternal)));
}
@Test
@@ -1406,7 +1414,7 @@
mBinderService.setNotificationsEnabledForPackage(mContext.getPackageName(), mUid, false);
verify(mPermissionHelper).setNotificationPermission(
- mContext.getPackageName(), UserHandle.getUserId(mUid), false, true);
+ mContext.getPackageName(), mUserId, false, true);
verify(mAppOpsManager, never()).setMode(anyInt(), anyInt(), anyString(), anyInt());
List<NotificationChannelLoggerFake.CallRecord> calls = mLogger.getCalls();
@@ -3123,7 +3131,7 @@
@Test
public void testCreateChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3150,7 +3158,7 @@
@Test
public void testCreateChannelGroupNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
@@ -3169,7 +3177,7 @@
@Test
public void testUpdateChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
mTestNotificationChannel.setLightColor(Color.CYAN);
@@ -3186,7 +3194,7 @@
@Test
public void testDeleteChannelNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3203,7 +3211,7 @@
@Test
public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
@@ -3217,7 +3225,7 @@
@Test
public void testDeleteChannelGroupNotifyListener() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
mService.setPreferencesHelper(mPreferencesHelper);
@@ -3233,7 +3241,7 @@
@Test
public void testDeleteChannelGroupChecksForFgses() throws Exception {
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
CountDownLatch latch = new CountDownLatch(2);
mService.createNotificationChannelGroup(
@@ -3282,7 +3290,7 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
@@ -3302,7 +3310,7 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3324,7 +3332,7 @@
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
mListener.component = new ComponentName(PKG, PKG);
@@ -3350,7 +3358,7 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelsFromPrivilegedListener(
@@ -3363,7 +3371,7 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_cdm_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3382,7 +3390,7 @@
public void testGetNotificationChannelFromPrivilegedListener_assistant_success()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
@@ -3397,7 +3405,7 @@
public void testGetNotificationChannelFromPrivilegedListener_assistant_noAccess()
throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(false);
@@ -3416,7 +3424,7 @@
@Test
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -3437,7 +3445,7 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
@@ -3449,7 +3457,7 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
try {
@@ -3466,7 +3474,7 @@
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
- when(mCompanionMgr.getAssociations(PKG, UserHandle.getUserId(mUid)))
+ when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
mListener = mock(ManagedServices.ManagedServiceInfo.class);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
@@ -4538,7 +4546,7 @@
// Same notifications are enqueued as posted, everything counts b/c id and tag don't match
// anything that's currently enqueued or posted
- int userId = UserHandle.getUserId(mUid);
+ int userId = mUserId;
assertEquals(40,
mService.getNotificationCount(PKG, userId, 0, null));
assertEquals(40,
@@ -6532,7 +6540,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6554,7 +6562,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6573,7 +6581,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6602,7 +6610,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6625,7 +6633,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6659,7 +6667,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6678,7 +6686,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6697,7 +6705,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6728,7 +6736,7 @@
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6750,7 +6758,7 @@
setAppInForegroundForToasts(mUid, true);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6771,7 +6779,7 @@
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6792,7 +6800,7 @@
setAppInForegroundForToasts(mUid, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
Binder token = new Binder();
@@ -6825,7 +6833,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6849,7 +6857,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, true);
@@ -6870,7 +6878,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
setAppInForegroundForToasts(mUid, false);
@@ -6883,20 +6891,81 @@
@Test
public void testTextToastsCallStatusBar() throws Exception {
- final String testPackage = "testPackageName";
- assertEquals(0, mService.mToastQueue.size());
- mService.isSystemUid = false;
- setToastRateIsWithinQuota(true);
- setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
-
- // package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
- .thenReturn(false);
+ allowTestPackageToToast();
// enqueue toast -> no toasts enqueued
- enqueueTextToast(testPackage, "Text");
- verify(mStatusBar).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(),
- anyInt());
+ enqueueTextToast(TEST_PACKAGE, "Text");
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_nonUiContext_defaultDisplay()
+ throws Exception {
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_nonUiContext_secondaryDisplay()
+ throws Exception {
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_defaultDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", DEFAULT_DISPLAY);
+
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_secondaryDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ true, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_defaultDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(SECONDARY_DISPLAY_ID);
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, DEFAULT_DISPLAY);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_secondaryDisplay()
+ throws Exception {
+ mockIsVisibleBackgroundUsersSupported(true);
+ mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used
+ allowTestPackageToToast();
+
+ enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID);
+
+ verifyToastShownForTestPackage("Text", SECONDARY_DISPLAY_ID);
}
@Test
@@ -6908,7 +6977,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(true);
// notifications from this package are NOT blocked by the user
@@ -6928,7 +6997,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
// notifications from this package are blocked by the user
@@ -6950,7 +7019,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(true);
// notifications from this package ARE blocked by the user
@@ -6972,7 +7041,7 @@
setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false);
// package is not suspended
- when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid)))
+ when(mPackageManager.isPackageSuspendedForUser(testPackage, mUserId))
.thenReturn(false);
INotificationManager nmService = (INotificationManager) mService.mService;
@@ -7002,7 +7071,7 @@
private void setIfPackageHasPermissionToAvoidToastRateLimiting(
String pkg, boolean hasPermission) throws Exception {
when(mPackageManager.checkPermission(android.Manifest.permission.UNLIMITED_TOASTS,
- pkg, UserHandle.getUserId(mUid)))
+ pkg, mUserId))
.thenReturn(hasPermission ? PERMISSION_GRANTED : PERMISSION_DENIED);
}
@@ -9611,7 +9680,7 @@
@Test
public void testMigrateNotificationFilter_migrationAllAllowed() throws Exception {
int uid = 9000;
- int[] userIds = new int[] {UserHandle.getUserId(mUid), 1000};
+ int[] userIds = new int[] {mUserId, 1000};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples", "bananas", "cherries");
for (int userId : userIds) {
@@ -9643,10 +9712,10 @@
@Test
public void testMigrateNotificationFilter_noPreexistingFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(null);
@@ -9664,10 +9733,10 @@
@Test
public void testMigrateNotificationFilter_existingTypeFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(
@@ -9687,10 +9756,10 @@
@Test
public void testMigrateNotificationFilter_existingPkgFilter() throws Exception {
- int[] userIds = new int[] {UserHandle.getUserId(mUid)};
+ int[] userIds = new int[] {mUserId};
when(mUm.getProfileIds(anyInt(), anyBoolean())).thenReturn(userIds);
List<String> disallowedApps = ImmutableList.of("apples");
- when(mPackageManager.getPackageUid("apples", 0, UserHandle.getUserId(mUid)))
+ when(mPackageManager.getPackageUid("apples", 0, mUserId))
.thenReturn(1001);
NotificationListenerFilter preexisting = new NotificationListenerFilter();
@@ -10704,6 +10773,16 @@
/* makeDefault= */ false);
}
+ private void allowTestPackageToToast() throws Exception {
+ assertWithMessage("toast queue").that(mService.mToastQueue).isEmpty();
+ mService.isSystemUid = false;
+ setToastRateIsWithinQuota(true);
+ setIfPackageHasPermissionToAvoidToastRateLimiting(TEST_PACKAGE, false);
+ // package is not suspended
+ when(mPackageManager.isPackageSuspendedForUser(TEST_PACKAGE, mUserId))
+ .thenReturn(false);
+ }
+
private void enqueueToast(String testPackage, ITransientNotification callback)
throws RemoteException {
enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), callback);
@@ -10716,7 +10795,25 @@
}
private void enqueueTextToast(String testPackage, CharSequence text) throws RemoteException {
+ enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY);
+ }
+
+ private void enqueueTextToast(String testPackage, CharSequence text, boolean isUiContext,
+ int displayId) throws RemoteException {
((INotificationManager) mService.mService).enqueueTextToast(testPackage, new Binder(), text,
- TOAST_DURATION, /* isUiContext= */ true, DEFAULT_DISPLAY, /* textCallback= */ null);
+ TOAST_DURATION, isUiContext, displayId, /* textCallback= */ null);
+ }
+
+ private void mockIsVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId);
+ }
+
+ private void verifyToastShownForTestPackage(String text, int displayId) {
+ verify(mStatusBar).showToast(eq(mUid), eq(TEST_PACKAGE), any(), eq(text), any(),
+ eq(TOAST_DURATION), any(), eq(displayId));
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 695a72e..7a0961d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -202,7 +202,6 @@
// Exclude comparing IME insets because currently the simulated layout only focuses on the
// insets from status bar and navigation bar.
realInsetsState.removeSource(InsetsSource.ID_IME);
- realInsetsState.removeSource(InsetsState.ITYPE_CAPTION_BAR);
assertEquals(new ToStringComparatorWrapper<>(realInsetsState),
new ToStringComparatorWrapper<>(simulatedInsetsState));
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 884dcf2..a34094c 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -69,6 +69,7 @@
private static final Object sSingletonSync = new Object();
private static EventManager sEventManager;
private static SessionManager sSessionManager;
+ private static Object sLock = null;
/**
* Tracks whether user-activated extended logging is enabled.
@@ -388,6 +389,19 @@
}
/**
+ * Sets the main telecom sync lock used within Telecom. This is used when building log messages
+ * so that we can identify places in the code where we are doing something outside of the
+ * Telecom lock.
+ * @param lock The lock.
+ */
+ public static void setLock(Object lock) {
+ // Don't do lock monitoring on user builds.
+ if (!Build.IS_USER) {
+ sLock = lock;
+ }
+ }
+
+ /**
* If user enabled extended logging is enabled and the time limit has passed, disables the
* extended logging.
*/
@@ -512,7 +526,10 @@
args.length);
msg = format + " (An error occurred while formatting the message.)";
}
- return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
+ // If a lock was set, check if this thread holds that lock and output an emoji that lets
+ // the developer know whether a log message came from within the Telecom lock or not.
+ String isLocked = sLock != null ? (Thread.holdsLock(sLock) ? "\uD83D\uDD12" : "❗") : "";
+ return String.format(Locale.US, "%s: %s%s%s", prefix, msg, sessionPostfix, isLocked);
}
/**
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9b7f244..c4a501d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -17914,6 +17914,97 @@
}
/**
+ * Captures parameters for collection of emergency
+ * call diagnostic data
+ * @hide
+ */
+ public static class EmergencyCallDiagnosticParams {
+
+ private boolean mCollectTelecomDumpSys;
+ private boolean mCollectTelephonyDumpsys;
+ private boolean mCollectLogcat;
+
+ //logcat lines with this time or greater are collected
+ //how much is collected is dependent on internal implementation.
+ //Time represented as milliseconds since January 1, 1970 UTC
+ private long mLogcatStartTimeMillis;
+
+
+ public boolean isTelecomDumpSysCollectionEnabled() {
+ return mCollectTelecomDumpSys;
+ }
+
+ public void setTelecomDumpSysCollection(boolean collectTelecomDumpSys) {
+ mCollectTelecomDumpSys = collectTelecomDumpSys;
+ }
+
+ public boolean isTelephonyDumpSysCollectionEnabled() {
+ return mCollectTelephonyDumpsys;
+ }
+
+ public void setTelephonyDumpSysCollection(boolean collectTelephonyDumpsys) {
+ mCollectTelephonyDumpsys = collectTelephonyDumpsys;
+ }
+
+ public boolean isLogcatCollectionEnabled() {
+ return mCollectLogcat;
+ }
+
+ public long getLogcatStartTime()
+ {
+ return mLogcatStartTimeMillis;
+ }
+
+ public void setLogcatCollection(boolean collectLogcat, long startTimeMillis) {
+ mCollectLogcat = collectLogcat;
+ if(mCollectLogcat)
+ {
+ mLogcatStartTimeMillis = startTimeMillis;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "EmergencyCallDiagnosticParams{" +
+ "mCollectTelecomDumpSys=" + mCollectTelecomDumpSys +
+ ", mCollectTelephonyDumpsys=" + mCollectTelephonyDumpsys +
+ ", mCollectLogcat=" + mCollectLogcat +
+ ", mLogcatStartTimeMillis=" + mLogcatStartTimeMillis +
+ '}';
+ }
+ }
+
+ /**
+ * Request telephony to persist state for debugging emergency call failures.
+ *
+ * @param dropboxTag Tag to use when persisting data to dropbox service.
+ *
+ * @see params Parameters controlling what is collected
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.DUMP)
+ public void persistEmergencyCallDiagnosticData(@NonNull String dropboxTag,
+ @NonNull EmergencyCallDiagnosticParams params) {
+ try {
+ ITelephony telephony = ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
+ if (telephony != null) {
+ telephony.persistEmergencyCallDiagnosticData(dropboxTag,
+ params.isLogcatCollectionEnabled(),
+ params.getLogcatStartTime(),
+ params.isTelecomDumpSysCollectionEnabled(),
+ params.isTelephonyDumpSysCollectionEnabled());
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while persistEmergencyCallDiagnosticData: " + e);
+ }
+ }
+
+ /**
* Set the UE's ability to accept/reject null ciphered and null integrity-protected connections.
*
* The modem is required to ignore this in case of an emergency call.
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d0de3ac..bab08b5 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2677,6 +2677,21 @@
int getSimStateForSlotIndex(int slotIndex);
/**
+ * Request telephony to persist state for debugging emergency call failures.
+ *
+ * @param dropBoxTag Tag to use when persisting data to dropbox service.
+ * @param enableLogcat whether to collect logcat output
+ * @param logcatStartTimestampMillis timestamp from when logcat buffers would be persisted
+ * @param enableTelecomDump whether to collect telecom dumpsys
+ * @param enableTelephonyDump whether to collect telephony dumpsys
+ *
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.DUMP)")
+ void persistEmergencyCallDiagnosticData(String dropboxTag, boolean enableLogcat,
+ long logcatStartTimestampMillis, boolean enableTelecomDump, boolean enableTelephonyDump);
+ /**
* Set whether the radio is able to connect with null ciphering or integrity
* algorithms. This is a global setting and will apply to all active subscriptions
* and all new subscriptions after this.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index dd9e4cf..3fccd12 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -219,9 +219,13 @@
val resourceId =
Resources.getSystem()
.getIdentifier("image_wallpaper_component", "string", "android")
- return ComponentNameMatcher.unflattenFromString(
+ // frameworks/base/core/res/res/values/config.xml returns package plus class name,
+ // but wallpaper layer has only class name
+ val rawComponentMatcher = ComponentNameMatcher.unflattenFromString(
instrumentation.targetContext.resources.getString(resourceId)
)
+
+ return ComponentNameMatcher(rawComponentMatcher.className)
}
@Parameterized.Parameters(name = "{0}")
diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
index 075bc5e..4123f80 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -17,7 +17,9 @@
package com.android.server;
import static android.net.ConnectivityManager.NetworkCallback;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -67,7 +69,6 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
-import android.net.TelephonyNetworkSpecifier;
import android.net.Uri;
import android.net.vcn.IVcnStatusCallback;
import android.net.vcn.IVcnUnderlyingNetworkPolicyListener;
@@ -128,6 +129,15 @@
private static final VcnConfig TEST_VCN_CONFIG;
private static final VcnConfig TEST_VCN_CONFIG_PKG_2;
private static final int TEST_UID = Process.FIRST_APPLICATION_UID;
+ private static final String TEST_IFACE_NAME = "TEST_IFACE";
+ private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2";
+ private static final LinkProperties TEST_LP_1 = new LinkProperties();
+ private static final LinkProperties TEST_LP_2 = new LinkProperties();
+
+ static {
+ TEST_LP_1.setInterfaceName(TEST_IFACE_NAME);
+ TEST_LP_2.setInterfaceName(TEST_IFACE_NAME_2);
+ }
static {
final Context mockConfigContext = mock(Context.class);
@@ -1034,8 +1044,7 @@
setupSubscriptionAndStartVcn(subId, subGrp, isVcnActive);
return mVcnMgmtSvc.getUnderlyingNetworkPolicy(
- getNetworkCapabilitiesBuilderForTransport(subId, transport).build(),
- new LinkProperties());
+ getNetworkCapabilitiesBuilderForTransport(subId, transport).build(), TEST_LP_1);
}
private void checkGetRestrictedTransportsFromCarrierConfig(
@@ -1260,7 +1269,7 @@
false /* expectRestricted */);
}
- private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) {
+ private void setupTrackedNetwork(NetworkCapabilities caps, LinkProperties lp) {
mVcnMgmtSvc.systemReady();
final ArgumentCaptor<NetworkCallback> captor =
@@ -1269,7 +1278,10 @@
.registerNetworkCallback(
eq(new NetworkRequest.Builder().clearCapabilities().build()),
captor.capture());
- captor.getValue().onCapabilitiesChanged(mock(Network.class, CALLS_REAL_METHODS), caps);
+
+ Network mockNetwork = mock(Network.class, CALLS_REAL_METHODS);
+ captor.getValue().onCapabilitiesChanged(mockNetwork, caps);
+ captor.getValue().onLinkPropertiesChanged(mockNetwork, lp);
}
@Test
@@ -1279,7 +1291,7 @@
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
// Trigger test without VCN instance alive; expect restart due to change of NOT_RESTRICTED
// immutable capability
@@ -1288,7 +1300,7 @@
getNetworkCapabilitiesBuilderForTransport(
TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.build(),
- new LinkProperties());
+ TEST_LP_1);
assertTrue(policy.isTeardownRequested());
}
@@ -1298,7 +1310,7 @@
final NetworkCapabilities existingNetworkCaps =
getNetworkCapabilitiesBuilderForTransport(TEST_SUBSCRIPTION_ID, TRANSPORT_WIFI)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
final VcnUnderlyingNetworkPolicy policy =
startVcnAndGetPolicyForTransport(
@@ -1315,7 +1327,7 @@
.addCapability(NET_CAPABILITY_NOT_RESTRICTED)
.removeCapability(NET_CAPABILITY_IMS)
.build();
- setupTrackedCarrierWifiNetwork(existingNetworkCaps);
+ setupTrackedNetwork(existingNetworkCaps, TEST_LP_1);
final VcnUnderlyingNetworkPolicy policy =
mVcnMgmtSvc.getUnderlyingNetworkPolicy(
@@ -1336,7 +1348,7 @@
new NetworkCapabilities.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .setNetworkSpecifier(new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_2))
+ .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
.build();
VcnUnderlyingNetworkPolicy policy =
@@ -1346,6 +1358,38 @@
assertEquals(nc, policy.getMergedNetworkCapabilities());
}
+ /**
+ * Checks that networks with similar capabilities do not clobber each other.
+ *
+ * <p>In previous iterations, the VcnMgmtSvc used capability-matching to check if a network
+ * undergoing policy checks were the same as an existing networks. However, this meant that if
+ * there were newly added capabilities that the VCN did not check, two networks differing only
+ * by that capability would restart each other constantly.
+ */
+ @Test
+ public void testGetUnderlyingNetworkPolicySimilarNetworks() throws Exception {
+ NetworkCapabilities nc1 =
+ new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .setSubscriptionIds(Collections.singleton(TEST_SUBSCRIPTION_ID_2))
+ .build();
+
+ NetworkCapabilities nc2 =
+ new NetworkCapabilities.Builder(nc1)
+ .addCapability(NET_CAPABILITY_ENTERPRISE)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+
+ setupTrackedNetwork(nc1, TEST_LP_1);
+
+ VcnUnderlyingNetworkPolicy policy = mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc2, TEST_LP_2);
+
+ assertFalse(policy.isTeardownRequested());
+ assertEquals(nc2, policy.getMergedNetworkCapabilities());
+ }
+
@Test(expected = SecurityException.class)
public void testGetUnderlyingNetworkPolicyInvalidPermission() {
doReturn(PackageManager.PERMISSION_DENIED)
diff --git a/wifi/java/src/android/net/wifi/nl80211/OWNERS b/wifi/java/src/android/net/wifi/nl80211/OWNERS
new file mode 100644
index 0000000..8a75e25
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/OWNERS
@@ -0,0 +1 @@
+kumachang@google.com