[automerger skipped] Merge "Bugfix: Allow shortcut and widget services to update shortcuts and widgets across all launchers during archival." into 24D1-dev am: e7a400e9f2 -s ours
am skip reason: Merged-In I6bdfddb889a80e698e9d62bf32022e4f59b0245a with SHA-1 c3ba6cb06c is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/26476215
Change-Id: If1d4da1cf714d8dc55c5bee3efb41880af0d1403
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2047168..a80194c 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -310,6 +310,8 @@
aconfig_declarations {
name: "android.os.flags-aconfig",
package: "android.os",
+ exportable: true,
+ container: "system",
srcs: ["core/java/android/os/*.aconfig"],
}
@@ -326,6 +328,24 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.os.flags-aconfig-java-export",
+ aconfig_declarations: "android.os.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ mode: "exported",
+}
+
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc",
+ aconfig_declarations: "android.os.flags-aconfig",
+}
+
+cc_aconfig_library {
+ name: "android.os.flags-aconfig-cc-test",
+ aconfig_declarations: "android.os.flags-aconfig",
+ mode: "test",
+}
+
// VirtualDeviceManager
cc_aconfig_library {
name: "android.companion.virtualdevice.flags-aconfig-cc",
@@ -483,6 +503,13 @@
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+java_aconfig_library {
+ name: "android.content.res.flags-aconfig-java-host",
+ aconfig_declarations: "android.content.res.flags-aconfig",
+ host_supported: true,
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media BetterTogether
aconfig_declarations {
name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/PACKAGE_MANAGER_OWNERS b/PACKAGE_MANAGER_OWNERS
index eb5842b..45719a7 100644
--- a/PACKAGE_MANAGER_OWNERS
+++ b/PACKAGE_MANAGER_OWNERS
@@ -1,3 +1,6 @@
+# Bug component: 36137
+# Bug template url: https://b.corp.google.com/issues/new?component=36137&template=198919
+
alexbuy@google.com
patb@google.com
schfan@google.com
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index e96d07f..ee9400f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -46,8 +46,11 @@
import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -68,6 +71,8 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Set;
import java.util.function.Predicate;
/**
@@ -1620,9 +1625,21 @@
private final Object mSatLock = new Object();
private DeviceIdleInternal mDeviceIdleInternal;
+ private TelephonyManager mTelephonyManager;
+
+ private final boolean mHasFeatureTelephonySubscription;
/** Set of all apps that have been deemed special, keyed by user ID. */
private final SparseSetArray<String> mSpecialApps = new SparseSetArray<>();
+ /**
+ * Set of carrier privileged apps, keyed by the logical ID of the SIM their privileged
+ * for.
+ */
+ @GuardedBy("mSatLock")
+ private final SparseSetArray<String> mCarrierPrivilegedApps = new SparseSetArray<>();
+ @GuardedBy("mSatLock")
+ private final SparseArray<LogicalIndexCarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
@GuardedBy("mSatLock")
private final ArraySet<String> mPowerAllowlistedApps = new ArraySet<>();
@@ -1630,6 +1647,10 @@
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
+ case TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED:
+ updateCarrierPrivilegedCallbackRegistration();
+ break;
+
case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
break;
@@ -1637,6 +1658,11 @@
}
};
+ SpecialAppTracker() {
+ mHasFeatureTelephonySubscription = mContext.getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+ }
+
public boolean isSpecialApp(final int userId, @NonNull String packageName) {
synchronized (mSatLock) {
if (mSpecialApps.contains(UserHandle.USER_ALL, packageName)) {
@@ -1654,6 +1680,12 @@
if (mPowerAllowlistedApps.contains(packageName)) {
return true;
}
+ for (int l = mCarrierPrivilegedApps.size() - 1; l >= 0; --l) {
+ if (mCarrierPrivilegedApps.contains(
+ mCarrierPrivilegedApps.keyAt(l), packageName)) {
+ return true;
+ }
+ }
}
return false;
}
@@ -1669,9 +1701,12 @@
private void onSystemServicesReady() {
mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class);
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
synchronized (mLock) {
if (mFlexibilityEnabled) {
+ mHandler.post(
+ SpecialAppTracker.this::updateCarrierPrivilegedCallbackRegistration);
mHandler.post(SpecialAppTracker.this::updatePowerAllowlistCache);
}
}
@@ -1686,6 +1721,13 @@
private void startTracking() {
IntentFilter filter = new IntentFilter(
PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
+
+ if (mHasFeatureTelephonySubscription) {
+ filter.addAction(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+
+ updateCarrierPrivilegedCallbackRegistration();
+ }
+
mContext.registerReceiver(mBroadcastReceiver, filter);
updatePowerAllowlistCache();
@@ -1695,11 +1737,63 @@
mContext.unregisterReceiver(mBroadcastReceiver);
synchronized (mSatLock) {
+ mCarrierPrivilegedApps.clear();
mPowerAllowlistedApps.clear();
mSpecialApps.clear();
+
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(
+ mCarrierPrivilegedCallbacks.valueAt(i));
+ }
+ mCarrierPrivilegedCallbacks.clear();
}
}
+ private void updateCarrierPrivilegedCallbackRegistration() {
+ if (mTelephonyManager == null) {
+ return;
+ }
+ if (!mHasFeatureTelephonySubscription) {
+ return;
+ }
+
+ Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final IntArray callbacksToRemove = new IntArray();
+ for (int i = mCarrierPrivilegedCallbacks.size() - 1; i >= 0; --i) {
+ callbacksToRemove.add(mCarrierPrivilegedCallbacks.keyAt(i));
+ }
+ for (UiccSlotMapping mapping : simSlotMapping) {
+ final int logicalIndex = mapping.getLogicalSlotIndex();
+ if (mCarrierPrivilegedCallbacks.contains(logicalIndex)) {
+ // Callback already exists. No need to create a new one or remove it.
+ callbacksToRemove.remove(logicalIndex);
+ continue;
+ }
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ new LogicalIndexCarrierPrivilegesCallback(logicalIndex);
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+ // Upon registration, the callbacks will be called with the current list of
+ // apps, so there's no need to query the app list synchronously.
+ mTelephonyManager.registerCarrierPrivilegesCallback(logicalIndex,
+ AppSchedulingModuleThread.getExecutor(), callback);
+ }
+
+ for (int i = callbacksToRemove.size() - 1; i >= 0; --i) {
+ final int logicalIndex = callbacksToRemove.get(i);
+ final LogicalIndexCarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ mTelephonyManager.unregisterCarrierPrivilegesCallback(callback);
+ mCarrierPrivilegedCallbacks.remove(logicalIndex);
+ changedPkgs.addAll(mCarrierPrivilegedApps.get(logicalIndex));
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
+ }
+
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+ }
+
/**
* Update the processed special app set for the specified user ID, only looking at the
* specified set of apps. This method must <b>NEVER</b> be called while holding
@@ -1762,18 +1856,65 @@
updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
}
+ class LogicalIndexCarrierPrivilegesCallback implements
+ TelephonyManager.CarrierPrivilegesCallback {
+ public final int logicalIndex;
+
+ LogicalIndexCarrierPrivilegesCallback(int logicalIndex) {
+ this.logicalIndex = logicalIndex;
+ }
+
+ @Override
+ public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
+ @NonNull Set<Integer> privilegedUids) {
+ final ArraySet<String> changedPkgs = new ArraySet<>();
+ synchronized (mSatLock) {
+ final ArraySet<String> oldPrivilegedSet =
+ mCarrierPrivilegedApps.get(logicalIndex);
+ if (oldPrivilegedSet != null) {
+ changedPkgs.addAll(oldPrivilegedSet);
+ mCarrierPrivilegedApps.remove(logicalIndex);
+ }
+ for (String pkgName : privilegedPackageNames) {
+ mCarrierPrivilegedApps.add(logicalIndex, pkgName);
+ if (!changedPkgs.remove(pkgName)) {
+ // The package wasn't in the previous set of privileged apps. Add it
+ // since its state has changed.
+ changedPkgs.add(pkgName);
+ }
+ }
+ }
+
+ // The carrier privileged list doesn't provide a simple userId correlation,
+ // so for now, use USER_ALL for these packages.
+ // TODO(141645789): use the UID list to narrow down to specific userIds
+ updateSpecialAppSetUnlocked(UserHandle.USER_ALL, changedPkgs);
+ }
+ }
+
public void dump(@NonNull IndentingPrintWriter pw) {
pw.println("Special apps:");
pw.increaseIndent();
synchronized (mSatLock) {
for (int u = 0; u < mSpecialApps.size(); ++u) {
+ pw.print("User ");
pw.print(mSpecialApps.keyAt(u));
pw.print(": ");
pw.println(mSpecialApps.valuesAt(u));
}
pw.println();
+ pw.println("Carrier privileged packages:");
+ pw.increaseIndent();
+ for (int i = 0; i < mCarrierPrivilegedApps.size(); ++i) {
+ pw.print(mCarrierPrivilegedApps.keyAt(i));
+ pw.print(": ");
+ pw.println(mCarrierPrivilegedApps.valuesAt(i));
+ }
+ pw.decreaseIndent();
+
+ pw.println();
pw.print("Power allowlisted packages: ");
pw.println(mPowerAllowlistedApps);
}
diff --git a/api/Android.bp b/api/Android.bp
index 8e06366..093ee4b 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -298,6 +298,28 @@
"org.xmlpull",
]
+// These are libs from framework-internal-utils that are required (i.e. being referenced)
+// from framework-non-updatable-sources. Add more here when there's a need.
+// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
+// dependencies gets bigger.
+android_non_updatable_stubs_libs = [
+ "android.hardware.cas-V1.2-java",
+ "android.hardware.health-V1.0-java-constants",
+ "android.hardware.thermal-V1.0-java-constants",
+ "android.hardware.thermal-V2.0-java",
+ "android.hardware.tv.input-V1.0-java-constants",
+ "android.hardware.usb-V1.0-java-constants",
+ "android.hardware.usb-V1.1-java-constants",
+ "android.hardware.usb.gadget-V1.0-java",
+ "android.hardware.vibrator-V1.3-java",
+ "framework-protos",
+]
+
+java_defaults {
+ name: "android-non-updatable-stubs-libs-defaults",
+ libs: android_non_updatable_stubs_libs,
+}
+
// Defaults for all stubs that include the non-updatable framework. These defaults do not include
// module symbols, so will not compile correctly on their own. Users must add module APIs to the
// classpath (or sources) somehow.
@@ -329,18 +351,7 @@
// from framework-non-updatable-sources. Add more here when there's a need.
// DO NOT add the entire framework-internal-utils. It might cause unnecessary circular
// dependencies gets bigger.
- libs: [
- "android.hardware.cas-V1.2-java",
- "android.hardware.health-V1.0-java-constants",
- "android.hardware.thermal-V1.0-java-constants",
- "android.hardware.thermal-V2.0-java",
- "android.hardware.tv.input-V1.0-java-constants",
- "android.hardware.usb-V1.0-java-constants",
- "android.hardware.usb-V1.1-java-constants",
- "android.hardware.usb.gadget-V1.0-java",
- "android.hardware.vibrator-V1.3-java",
- "framework-protos",
- ],
+ libs: android_non_updatable_stubs_libs,
flags: [
"--error NoSettingsProvider",
"--error UnhiddenSystemApi",
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 8b8d361..a142450 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -134,8 +134,9 @@
return env;
}
-std::unique_ptr<Device> Device::open(int32_t id, const char* name, int32_t vid, int32_t pid,
- uint16_t bus, const std::vector<uint8_t>& descriptor,
+std::unique_ptr<Device> Device::open(int32_t id, const char* name, const char* uniq, int32_t vid,
+ int32_t pid, uint16_t bus,
+ const std::vector<uint8_t>& descriptor,
std::unique_ptr<DeviceCallback> callback) {
size_t size = descriptor.size();
if (size > HID_MAX_DESCRIPTOR_SIZE) {
@@ -152,8 +153,7 @@
struct uhid_event ev = {};
ev.type = UHID_CREATE2;
strlcpy(reinterpret_cast<char*>(ev.u.create2.name), name, sizeof(ev.u.create2.name));
- std::string uniq = android::base::StringPrintf("Id: %d", id);
- strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq.c_str(), sizeof(ev.u.create2.uniq));
+ strlcpy(reinterpret_cast<char*>(ev.u.create2.uniq), uniq, sizeof(ev.u.create2.uniq));
memcpy(&ev.u.create2.rd_data, descriptor.data(), size * sizeof(ev.u.create2.rd_data[0]));
ev.u.create2.rd_size = size;
ev.u.create2.bus = bus;
@@ -314,19 +314,31 @@
return data;
}
-static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jint id, jint vid,
- jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
+static jlong openDevice(JNIEnv* env, jclass /* clazz */, jstring rawName, jstring rawUniq, jint id,
+ jint vid, jint pid, jint bus, jbyteArray rawDescriptor, jobject callback) {
ScopedUtfChars name(env, rawName);
if (name.c_str() == nullptr) {
return 0;
}
+ std::string uniq;
+ if (rawUniq != nullptr) {
+ uniq = ScopedUtfChars(env, rawUniq);
+ } else {
+ uniq = android::base::StringPrintf("Id: %d", id);
+ }
+
+ if (uniq.c_str() == nullptr) {
+ return 0;
+ }
+
std::vector<uint8_t> desc = getData(env, rawDescriptor);
std::unique_ptr<uhid::DeviceCallback> cb(new uhid::DeviceCallback(env, callback));
std::unique_ptr<uhid::Device> d =
- uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()), vid, pid, bus, desc,
+ uhid::Device::open(id, reinterpret_cast<const char*>(name.c_str()),
+ reinterpret_cast<const char*>(uniq.c_str()), vid, pid, bus, desc,
std::move(cb));
return reinterpret_cast<jlong>(d.release());
}
@@ -370,7 +382,7 @@
static JNINativeMethod sMethods[] = {
{"nativeOpenDevice",
- "(Ljava/lang/String;IIII[B"
+ "(Ljava/lang/String;Ljava/lang/String;IIII[B"
"Lcom/android/commands/hid/Device$DeviceCallback;)J",
reinterpret_cast<void*>(openDevice)},
{"nativeSendReport", "(J[B)V", reinterpret_cast<void*>(sendReport)},
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h
index 9c6060d..bc7a909 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.h
+++ b/cmds/hid/jni/com_android_commands_hid_Device.h
@@ -42,8 +42,9 @@
class Device {
public:
- static std::unique_ptr<Device> open(int32_t id, const char* name, int32_t vid, int32_t pid,
- uint16_t bus, const std::vector<uint8_t>& descriptor,
+ static std::unique_ptr<Device> open(int32_t id, const char* name, const char* uniq, int32_t vid,
+ int32_t pid, uint16_t bus,
+ const std::vector<uint8_t>& descriptor,
std::unique_ptr<DeviceCallback> callback);
~Device();
diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java
index 0415037..4e8adc3 100644
--- a/cmds/hid/src/com/android/commands/hid/Device.java
+++ b/cmds/hid/src/com/android/commands/hid/Device.java
@@ -71,6 +71,7 @@
private static native long nativeOpenDevice(
String name,
+ String uniq,
int id,
int vid,
int pid,
@@ -89,6 +90,7 @@
public Device(
int id,
String name,
+ String uniq,
int vid,
int pid,
int bus,
@@ -113,8 +115,9 @@
} else {
args.arg1 = id + ":" + vid + ":" + pid;
}
- args.arg2 = descriptor;
- args.arg3 = report;
+ args.arg2 = uniq;
+ args.arg3 = descriptor;
+ args.arg4 = report;
mHandler.obtainMessage(MSG_OPEN_DEVICE, args).sendToTarget();
mTimeToSend = SystemClock.uptimeMillis();
}
@@ -167,11 +170,12 @@
mPtr =
nativeOpenDevice(
(String) args.arg1,
+ (String) args.arg2,
args.argi1,
args.argi2,
args.argi3,
args.argi4,
- (byte[]) args.arg2,
+ (byte[]) args.arg3,
new DeviceCallback());
pauseEvents();
break;
diff --git a/cmds/hid/src/com/android/commands/hid/Event.java b/cmds/hid/src/com/android/commands/hid/Event.java
index 3efb797..09ed528 100644
--- a/cmds/hid/src/com/android/commands/hid/Event.java
+++ b/cmds/hid/src/com/android/commands/hid/Event.java
@@ -56,6 +56,7 @@
private int mId;
private String mCommand;
private String mName;
+ private String mUniq;
private byte[] mDescriptor;
private int mVid;
private int mPid;
@@ -78,6 +79,10 @@
return mName;
}
+ public String getUniq() {
+ return mUniq;
+ }
+
public byte[] getDescriptor() {
return mDescriptor;
}
@@ -118,6 +123,7 @@
return "Event{id=" + mId
+ ", command=" + String.valueOf(mCommand)
+ ", name=" + String.valueOf(mName)
+ + ", uniq=" + String.valueOf(mUniq)
+ ", descriptor=" + Arrays.toString(mDescriptor)
+ ", vid=" + mVid
+ ", pid=" + mPid
@@ -149,6 +155,10 @@
mEvent.mName = name;
}
+ public void setUniq(String uniq) {
+ mEvent.mUniq = uniq;
+ }
+
public void setDescriptor(byte[] descriptor) {
mEvent.mDescriptor = descriptor;
}
@@ -247,6 +257,9 @@
case "name":
eb.setName(mReader.nextString());
break;
+ case "uniq":
+ eb.setUniq(mReader.nextString());
+ break;
case "vid":
eb.setVid(readInt());
break;
diff --git a/cmds/hid/src/com/android/commands/hid/Hid.java b/cmds/hid/src/com/android/commands/hid/Hid.java
index 2db791fe..5ebfd95 100644
--- a/cmds/hid/src/com/android/commands/hid/Hid.java
+++ b/cmds/hid/src/com/android/commands/hid/Hid.java
@@ -117,8 +117,17 @@
"Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
}
int id = e.getId();
- Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(), e.getBus(),
- e.getDescriptor(), e.getReport(), e.getFeatureReports(), e.getOutputs());
+ Device d = new Device(
+ id,
+ e.getName(),
+ e.getUniq(),
+ e.getVendorId(),
+ e.getProductId(),
+ e.getBus(),
+ e.getDescriptor(),
+ e.getReport(),
+ e.getFeatureReports(),
+ e.getOutputs());
mDevices.append(id, d);
}
diff --git a/cmds/telecom/Android.bp b/cmds/telecom/Android.bp
index be02710..494d2ae3 100644
--- a/cmds/telecom/Android.bp
+++ b/cmds/telecom/Android.bp
@@ -21,5 +21,8 @@
java_binary {
name: "telecom",
wrapper: "telecom.sh",
- srcs: ["**/*.java"],
+ srcs: [
+ ":telecom-shell-commands-src",
+ "**/*.java",
+ ],
}
diff --git a/cmds/telecom/src/com/android/commands/telecom/Telecom.java b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
index 1488e14cf..50af5a7 100644
--- a/cmds/telecom/src/com/android/commands/telecom/Telecom.java
+++ b/cmds/telecom/src/com/android/commands/telecom/Telecom.java
@@ -17,30 +17,22 @@
package com.android.commands.telecom;
import android.app.ActivityThread;
-import android.content.ComponentName;
import android.content.Context;
-import android.net.Uri;
-import android.os.IUserManager;
import android.os.Looper;
-import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.sysprop.TelephonyProperties;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-import com.android.internal.os.BaseCommand;
import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.TelecomShellCommand;
-import java.io.PrintStream;
-import java.util.Arrays;
-import java.util.stream.Collectors;
+import java.io.FileDescriptor;
-public final class Telecom extends BaseCommand {
+/**
+ * @deprecated Use {@code com.android.server.telecom.TelecomShellCommand} instead and execute the
+ * shell command using {@code adb shell cmd telecom...}. This is only here for backwards
+ * compatibility reasons.
+ */
+@Deprecated
+public final class Telecom {
/**
* Command-line entry point.
@@ -52,458 +44,11 @@
// TODO: Do it in zygote and RuntimeInit. b/148897549
ActivityThread.initializeMainlineModules();
- (new Telecom()).run(args);
- }
- private static final String CALLING_PACKAGE = Telecom.class.getPackageName();
- private static final String COMMAND_SET_PHONE_ACCOUNT_ENABLED = "set-phone-account-enabled";
- private static final String COMMAND_SET_PHONE_ACCOUNT_DISABLED = "set-phone-account-disabled";
- private static final String COMMAND_REGISTER_PHONE_ACCOUNT = "register-phone-account";
- private static final String COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT =
- "set-user-selected-outgoing-phone-account";
- private static final String COMMAND_REGISTER_SIM_PHONE_ACCOUNT = "register-sim-phone-account";
- private static final String COMMAND_SET_TEST_CALL_REDIRECTION_APP = "set-test-call-redirection-app";
- private static final String COMMAND_SET_TEST_CALL_SCREENING_APP = "set-test-call-screening-app";
- private static final String COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP =
- "add-or-remove-call-companion-app";
- private static final String COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT =
- "set-phone-acct-suggestion-component";
- private static final String COMMAND_UNREGISTER_PHONE_ACCOUNT = "unregister-phone-account";
- private static final String COMMAND_SET_CALL_DIAGNOSTIC_SERVICE = "set-call-diagnostic-service";
- private static final String COMMAND_SET_DEFAULT_DIALER = "set-default-dialer";
- private static final String COMMAND_GET_DEFAULT_DIALER = "get-default-dialer";
- private static final String COMMAND_STOP_BLOCK_SUPPRESSION = "stop-block-suppression";
- private static final String COMMAND_CLEANUP_STUCK_CALLS = "cleanup-stuck-calls";
- private static final String COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS =
- "cleanup-orphan-phone-accounts";
- private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
- private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
- "is-non-ui-in-call-service-bound";
-
- /**
- * Change the system dialer package name if a package name was specified,
- * Example: adb shell telecom set-system-dialer <PACKAGE>
- *
- * Restore it to the default if if argument is "default" or no argument is passed.
- * Example: adb shell telecom set-system-dialer default
- */
- private static final String COMMAND_SET_SYSTEM_DIALER = "set-system-dialer";
- private static final String COMMAND_GET_SYSTEM_DIALER = "get-system-dialer";
- private static final String COMMAND_WAIT_ON_HANDLERS = "wait-on-handlers";
- private static final String COMMAND_SET_SIM_COUNT = "set-sim-count";
- private static final String COMMAND_GET_SIM_CONFIG = "get-sim-config";
- private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
- private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
- "set-test-emergency-phone-account-package-filter";
- /**
- * Command used to emit a distinct "mark" in the logs.
- */
- private static final String COMMAND_LOG_MARK = "log-mark";
-
- private ComponentName mComponent;
- private String mAccountId;
- private ITelecomService mTelecomService;
- private TelephonyManager mTelephonyManager;
- private IUserManager mUserManager;
-
- @Override
- public void onShowUsage(PrintStream out) {
- out.println("usage: telecom [subcommand] [options]\n"
- + "usage: telecom set-phone-account-enabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-phone-account-disabled <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom register-phone-account <COMPONENT> <ID> <USER_SN> <LABEL>\n"
- + "usage: telecom register-sim-phone-account [-e] <COMPONENT> <ID> <USER_SN>"
- + " <LABEL>: registers a PhoneAccount with CAPABILITY_SIM_SUBSCRIPTION"
- + " and optionally CAPABILITY_PLACE_EMERGENCY_CALLS if \"-e\" is provided\n"
- + "usage: telecom set-user-selected-outgoing-phone-account [-e] <COMPONENT> <ID> "
- + "<USER_SN>\n"
- + "usage: telecom set-test-call-redirection-app <PACKAGE>\n"
- + "usage: telecom set-test-call-screening-app <PACKAGE>\n"
- + "usage: telecom set-phone-acct-suggestion-component <COMPONENT>\n"
- + "usage: telecom add-or-remove-call-companion-app <PACKAGE> <1/0>\n"
- + "usage: telecom register-sim-phone-account <COMPONENT> <ID> <USER_SN>"
- + " <LABEL> <ADDRESS>\n"
- + "usage: telecom unregister-phone-account <COMPONENT> <ID> <USER_SN>\n"
- + "usage: telecom set-call-diagnostic-service <PACKAGE>\n"
- + "usage: telecom set-default-dialer <PACKAGE>\n"
- + "usage: telecom get-default-dialer\n"
- + "usage: telecom get-system-dialer\n"
- + "usage: telecom wait-on-handlers\n"
- + "usage: telecom set-sim-count <COUNT>\n"
- + "usage: telecom get-sim-config\n"
- + "usage: telecom get-max-phones\n"
- + "usage: telecom stop-block-suppression: Stop suppressing the blocked number"
- + " provider after a call to emergency services.\n"
- + "usage: telecom cleanup-stuck-calls: Clear any disconnected calls that have"
- + " gotten wedged in Telecom.\n"
- + "usage: telecom cleanup-orphan-phone-accounts: remove any phone accounts that"
- + " no longer have a valid UserHandle or accounts that no longer belongs to an"
- + " installed package.\n"
- + "usage: telecom set-emer-phone-account-filter <PACKAGE>\n"
- + "\n"
- + "telecom set-phone-account-enabled: Enables the given phone account, if it has"
- + " already been registered with Telecom.\n"
- + "\n"
- + "telecom set-phone-account-disabled: Disables the given phone account, if it"
- + " has already been registered with telecom.\n"
- + "\n"
- + "telecom set-call-diagnostic-service: overrides call diagnostic service.\n"
- + "telecom set-default-dialer: Sets the override default dialer to the given"
- + " component; this will override whatever the dialer role is set to.\n"
- + "\n"
- + "telecom get-default-dialer: Displays the current default dialer.\n"
- + "\n"
- + "telecom get-system-dialer: Displays the current system dialer.\n"
- + "telecom set-system-dialer: Set the override system dialer to the given"
- + " component. To remove the override, send \"default\"\n"
- + "\n"
- + "telecom wait-on-handlers: Wait until all handlers finish their work.\n"
- + "\n"
- + "telecom set-sim-count: Set num SIMs (2 for DSDS, 1 for single SIM."
- + " This may restart the device.\n"
- + "\n"
- + "telecom get-sim-config: Get the mSIM config string. \"DSDS\" for DSDS mode,"
- + " or \"\" for single SIM\n"
- + "\n"
- + "telecom get-max-phones: Get the max supported phones from the modem.\n"
- + "telecom set-test-emergency-phone-account-package-filter <PACKAGE>: sets a"
- + " package name that will be used for test emergency calls. To clear,"
- + " send an empty package name. Real emergency calls will still be placed"
- + " over Telephony.\n"
- + "telecom log-mark <MESSAGE>: emits a message into the telecom logs. Useful for "
- + "testers to indicate where in the logs various test steps take place.\n"
- + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
- + "non-ui-InCallService in InCallController to determine if it is bound \n"
- );
- }
-
- @Override
- public void onRun() throws Exception {
- mTelecomService = ITelecomService.Stub.asInterface(
- ServiceManager.getService(Context.TELECOM_SERVICE));
- if (mTelecomService == null) {
- Log.w(this, "onRun: Can't access telecom manager.");
- showError("Error: Could not access the Telecom Manager. Is the system running?");
- return;
- }
-
Looper.prepareMainLooper();
+ ITelecomService service = ITelecomService.Stub.asInterface(
+ ServiceManager.getService(Context.TELECOM_SERVICE));
Context context = ActivityThread.systemMain().getSystemContext();
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- if (mTelephonyManager == null) {
- Log.w(this, "onRun: Can't access telephony service.");
- showError("Error: Could not access the Telephony Service. Is the system running?");
- return;
- }
-
- mUserManager = IUserManager.Stub
- .asInterface(ServiceManager.getService(Context.USER_SERVICE));
- if (mUserManager == null) {
- Log.w(this, "onRun: Can't access user manager.");
- showError("Error: Could not access the User Manager. Is the system running?");
- return;
- }
- Log.i(this, "onRun: parsing command.");
- String command = nextArgRequired();
- switch (command) {
- case COMMAND_SET_PHONE_ACCOUNT_ENABLED:
- runSetPhoneAccountEnabled(true);
- break;
- case COMMAND_SET_PHONE_ACCOUNT_DISABLED:
- runSetPhoneAccountEnabled(false);
- break;
- case COMMAND_REGISTER_PHONE_ACCOUNT:
- runRegisterPhoneAccount();
- break;
- case COMMAND_SET_TEST_CALL_REDIRECTION_APP:
- runSetTestCallRedirectionApp();
- break;
- case COMMAND_SET_TEST_CALL_SCREENING_APP:
- runSetTestCallScreeningApp();
- break;
- case COMMAND_ADD_OR_REMOVE_CALL_COMPANION_APP:
- runAddOrRemoveCallCompanionApp();
- break;
- case COMMAND_SET_PHONE_ACCOUNT_SUGGESTION_COMPONENT:
- runSetTestPhoneAcctSuggestionComponent();
- break;
- case COMMAND_SET_CALL_DIAGNOSTIC_SERVICE:
- runSetCallDiagnosticService();
- break;
- case COMMAND_REGISTER_SIM_PHONE_ACCOUNT:
- runRegisterSimPhoneAccount();
- break;
- case COMMAND_SET_USER_SELECTED_OUTGOING_PHONE_ACCOUNT:
- runSetUserSelectedOutgoingPhoneAccount();
- break;
- case COMMAND_UNREGISTER_PHONE_ACCOUNT:
- runUnregisterPhoneAccount();
- break;
- case COMMAND_STOP_BLOCK_SUPPRESSION:
- runStopBlockSuppression();
- break;
- case COMMAND_CLEANUP_STUCK_CALLS:
- runCleanupStuckCalls();
- break;
- case COMMAND_CLEANUP_ORPHAN_PHONE_ACCOUNTS:
- runCleanupOrphanPhoneAccounts();
- break;
- case COMMAND_RESET_CAR_MODE:
- runResetCarMode();
- break;
- case COMMAND_SET_DEFAULT_DIALER:
- runSetDefaultDialer();
- break;
- case COMMAND_GET_DEFAULT_DIALER:
- runGetDefaultDialer();
- break;
- case COMMAND_SET_SYSTEM_DIALER:
- runSetSystemDialer();
- break;
- case COMMAND_GET_SYSTEM_DIALER:
- runGetSystemDialer();
- break;
- case COMMAND_WAIT_ON_HANDLERS:
- runWaitOnHandler();
- break;
- case COMMAND_SET_SIM_COUNT:
- runSetSimCount();
- break;
- case COMMAND_GET_SIM_CONFIG:
- runGetSimConfig();
- break;
- case COMMAND_GET_MAX_PHONES:
- runGetMaxPhones();
- break;
- case COMMAND_IS_NON_IN_CALL_SERVICE_BOUND:
- runIsNonUiInCallServiceBound();
- break;
- case COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER:
- runSetEmergencyPhoneAccountPackageFilter();
- break;
- case COMMAND_LOG_MARK:
- runLogMark();
- break;
- default:
- Log.w(this, "onRun: unknown command: %s", command);
- throw new IllegalArgumentException ("unknown command '" + command + "'");
- }
- }
-
- private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final boolean success = mTelecomService.enablePhoneAccount(handle, enabled);
- if (success) {
- System.out.println("Success - " + handle + (enabled ? " enabled." : " disabled."));
- } else {
- System.out.println("Error - is " + handle + " a valid PhoneAccount?");
- }
- }
-
- private void runRegisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- PhoneAccount account = PhoneAccount.builder(handle, label)
- .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runRegisterSimPhoneAccount() throws RemoteException {
- boolean isEmergencyAccount = false;
- String opt;
- while ((opt = nextOption()) != null) {
- switch (opt) {
- case "-e": {
- isEmergencyAccount = true;
- break;
- }
- }
- }
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- final String label = nextArgRequired();
- final String address = nextArgRequired();
- int capabilities = PhoneAccount.CAPABILITY_CALL_PROVIDER
- | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
- | (isEmergencyAccount ? PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS : 0);
- PhoneAccount account = PhoneAccount.builder(
- handle, label)
- .setAddress(Uri.parse(address))
- .setSubscriptionAddress(Uri.parse(address))
- .setCapabilities(capabilities)
- .setShortDescription(label)
- .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
- .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
- .build();
- mTelecomService.registerPhoneAccount(account, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " registered.");
- }
-
- private void runSetTestCallRedirectionApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallRedirectionApp(packageName);
- }
-
- private void runSetTestCallScreeningApp() throws RemoteException {
- final String packageName = nextArg();
- mTelecomService.setTestDefaultCallScreeningApp(packageName);
- }
-
- private void runAddOrRemoveCallCompanionApp() throws RemoteException {
- final String packageName = nextArgRequired();
- String isAdded = nextArgRequired();
- boolean isAddedBool = "1".equals(isAdded);
- mTelecomService.addOrRemoveTestCallCompanionApp(packageName, isAddedBool);
- }
-
- private void runSetCallDiagnosticService() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestCallDiagnosticService(packageName);
- System.out.println("Success - " + packageName + " set as call diagnostic service.");
- }
-
- private void runSetTestPhoneAcctSuggestionComponent() throws RemoteException {
- final String componentName = nextArg();
- mTelecomService.setTestPhoneAcctSuggestionComponent(componentName);
- }
-
- private void runSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
- Log.i(this, "runSetUserSelectedOutgoingPhoneAccount");
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.setUserSelectedOutgoingPhoneAccount(handle);
- System.out.println("Success - " + handle + " set as default outgoing account.");
- }
-
- private void runUnregisterPhoneAccount() throws RemoteException {
- final PhoneAccountHandle handle = getPhoneAccountHandleFromArgs();
- mTelecomService.unregisterPhoneAccount(handle, CALLING_PACKAGE);
- System.out.println("Success - " + handle + " unregistered.");
- }
-
- private void runStopBlockSuppression() throws RemoteException {
- mTelecomService.stopBlockSuppression();
- }
-
- private void runCleanupStuckCalls() throws RemoteException {
- mTelecomService.cleanupStuckCalls();
- }
-
- private void runCleanupOrphanPhoneAccounts() throws RemoteException {
- System.out.println("Success - cleaned up " + mTelecomService.cleanupOrphanPhoneAccounts()
- + " phone accounts.");
- }
-
- private void runResetCarMode() throws RemoteException {
- mTelecomService.resetCarMode();
- }
-
- private void runSetDefaultDialer() throws RemoteException {
- String packageName = nextArg();
- if ("default".equals(packageName)) packageName = null;
- mTelecomService.setTestDefaultDialer(packageName);
- System.out.println("Success - " + packageName + " set as override default dialer.");
- }
-
- private void runSetSystemDialer() throws RemoteException {
- final String flatComponentName = nextArg();
- final ComponentName componentName = (flatComponentName.equals("default")
- ? null : parseComponentName(flatComponentName));
- mTelecomService.setSystemDialer(componentName);
- System.out.println("Success - " + componentName + " set as override system dialer.");
- }
-
- private void runGetDefaultDialer() throws RemoteException {
- System.out.println(mTelecomService.getDefaultDialerPackage(CALLING_PACKAGE));
- }
-
- private void runGetSystemDialer() throws RemoteException {
- System.out.println(mTelecomService.getSystemDialerPackage(CALLING_PACKAGE));
- }
-
- private void runWaitOnHandler() throws RemoteException {
-
- }
-
- private void runSetSimCount() throws RemoteException {
- if (!callerIsRoot()) {
- System.out.println("set-sim-count requires adb root");
- return;
- }
- int numSims = Integer.parseInt(nextArgRequired());
- System.out.println("Setting sim count to " + numSims + ". Device may reboot");
- mTelephonyManager.switchMultiSimConfig(numSims);
- }
-
- /**
- * prints out whether a particular non-ui InCallServices is bound in a call
- */
- public void runIsNonUiInCallServiceBound() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- System.out.println("No Argument passed. Please pass a <PACKAGE_NAME> to lookup.");
- } else {
- System.out.println(
- String.valueOf(mTelecomService.isNonUiInCallServiceBound(nextArg())));
- }
- }
-
- /**
- * Prints the mSIM config to the console.
- * "DSDS" for a phone in DSDS mode
- * "" (empty string) for a phone in SS mode
- */
- private void runGetSimConfig() throws RemoteException {
- System.out.println(TelephonyProperties.multi_sim_config().orElse(""));
- }
-
- private void runGetMaxPhones() throws RemoteException {
- // how many logical modems can be potentially active simultaneously
- System.out.println(mTelephonyManager.getSupportedModemCount());
- }
-
- private void runSetEmergencyPhoneAccountPackageFilter() throws RemoteException {
- String packageName = mArgs.getNextArg();
- if (TextUtils.isEmpty(packageName)) {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(null);
- System.out.println("Success - filter cleared");
- } else {
- mTelecomService.setTestEmergencyPhoneAccountPackageNameFilter(packageName);
- System.out.println("Success = filter set to " + packageName);
- }
-
- }
-
- private void runLogMark() throws RemoteException {
- String message = Arrays.stream(mArgs.peekRemainingArgs()).collect(Collectors.joining(" "));
- mTelecomService.requestLogMark(message);
- }
-
- private PhoneAccountHandle getPhoneAccountHandleFromArgs() throws RemoteException {
- if (TextUtils.isEmpty(mArgs.peekNextArg())) {
- return null;
- }
- final ComponentName component = parseComponentName(nextArgRequired());
- final String accountId = nextArgRequired();
- final String userSnInStr = nextArgRequired();
- UserHandle userHandle;
- try {
- final int userSn = Integer.parseInt(userSnInStr);
- userHandle = UserHandle.of(mUserManager.getUserHandle(userSn));
- } catch (NumberFormatException ex) {
- Log.w(this, "getPhoneAccountHandleFromArgs - invalid user %s", userSnInStr);
- throw new IllegalArgumentException ("Invalid user serial number " + userSnInStr);
- }
- return new PhoneAccountHandle(component, accountId, userHandle);
- }
-
- private boolean callerIsRoot() {
- return Process.ROOT_UID == Process.myUid();
- }
-
- private ComponentName parseComponentName(String component) {
- ComponentName cn = ComponentName.unflattenFromString(component);
- if (cn == null) {
- throw new IllegalArgumentException ("Invalid component " + component);
- }
- return cn;
+ new TelecomShellCommand(service, context).exec(null, FileDescriptor.in,
+ FileDescriptor.out, FileDescriptor.err, args);
}
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 62980ed..2d783d3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4445,7 +4445,7 @@
method public final android.media.session.MediaController getMediaController();
method @NonNull public android.view.MenuInflater getMenuInflater();
method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
- method public final android.app.Activity getParent();
+ method @Deprecated public final android.app.Activity getParent();
method @Nullable public android.content.Intent getParentActivityIntent();
method public android.content.SharedPreferences getPreferences(int);
method @Nullable public android.net.Uri getReferrer();
@@ -4463,7 +4463,7 @@
method public void invalidateOptionsMenu();
method public boolean isActivityTransitionRunning();
method public boolean isChangingConfigurations();
- method public final boolean isChild();
+ method @Deprecated public final boolean isChild();
method public boolean isDestroyed();
method public boolean isFinishing();
method public boolean isImmersive();
@@ -5395,7 +5395,7 @@
public final class AutomaticZenRule implements android.os.Parcelable {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
- ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
+ ctor @Deprecated public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
method public int describeContents();
method public android.net.Uri getConditionId();
@@ -6085,7 +6085,7 @@
public class GrammaticalInflectionManager {
method public int getApplicationGrammaticalGender();
- method @FlaggedApi("android.app.system_terms_of_address_enabled") public int getSystemGrammaticalGender();
+ method @FlaggedApi("android.app.system_terms_of_address_enabled") @RequiresPermission("android.permission.READ_SYSTEM_GRAMMATICAL_GENDER") public int getSystemGrammaticalGender();
method public void setRequestedApplicationGrammaticalGender(int);
}
@@ -6674,9 +6674,9 @@
method @Deprecated public android.app.Notification.Builder addPerson(String);
method @NonNull public android.app.Notification.Builder addPerson(android.app.Person);
method @NonNull public android.app.Notification build();
- method public android.widget.RemoteViews createBigContentView();
- method public android.widget.RemoteViews createContentView();
- method public android.widget.RemoteViews createHeadsUpContentView();
+ method @Deprecated public android.widget.RemoteViews createBigContentView();
+ method @Deprecated public android.widget.RemoteViews createContentView();
+ method @Deprecated public android.widget.RemoteViews createHeadsUpContentView();
method @NonNull public android.app.Notification.Builder extend(android.app.Notification.Extender);
method public android.os.Bundle getExtras();
method @Deprecated public android.app.Notification getNotification();
@@ -9608,7 +9608,7 @@
method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
method @FlaggedApi("android.appwidget.flags.generated_previews") public void removeWidgetPreview(@NonNull android.content.ComponentName, int);
method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
- method @FlaggedApi("android.appwidget.flags.generated_previews") public void setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
+ method @FlaggedApi("android.appwidget.flags.generated_previews") public boolean setWidgetPreview(@NonNull android.content.ComponentName, int, @NonNull android.widget.RemoteViews);
method public void updateAppWidget(int[], android.widget.RemoteViews);
method public void updateAppWidget(int, android.widget.RemoteViews);
method public void updateAppWidget(android.content.ComponentName, android.widget.RemoteViews);
@@ -10731,7 +10731,6 @@
field public static final String DROPBOX_SERVICE = "dropbox";
field public static final String EUICC_SERVICE = "euicc";
field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
- field public static final String FINGERPRINT_SERVICE = "fingerprint";
field public static final String GAME_SERVICE = "game";
field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
@@ -15708,7 +15707,7 @@
method public boolean clipRect(int, int, int, int);
method @FlaggedApi("com.android.graphics.hwui.flags.clip_shader") public void clipShader(@NonNull android.graphics.Shader);
method public void concat(@Nullable android.graphics.Matrix);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat44(@Nullable android.graphics.Matrix44);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void concat(@Nullable android.graphics.Matrix44);
method public void disableZ();
method public void drawARGB(int, int, int, int);
method public void drawArc(@NonNull android.graphics.RectF, float, float, boolean, @NonNull android.graphics.Paint);
@@ -16361,7 +16360,7 @@
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44();
ctor @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public Matrix44(@NonNull android.graphics.Matrix);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 concat(@NonNull android.graphics.Matrix44);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(int, int);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public float get(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void getValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean invert();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public boolean isIdentity();
@@ -16370,7 +16369,7 @@
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void reset();
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 rotate(float, float, float, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 scale(float, float, float);
- method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(int, int, float);
+ method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void set(@IntRange(from=0, to=3) int, @IntRange(from=0, to=3) int, float);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") public void setValues(@NonNull float[]);
method @FlaggedApi("com.android.graphics.hwui.flags.matrix_44") @NonNull public android.graphics.Matrix44 translate(float, float, float);
}
@@ -20379,54 +20378,6 @@
}
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
- method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
- field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
- field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
- field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
- field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
- field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
- field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
- field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
- field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
- field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
- field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
- field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
- field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
- field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
- field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
- field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
- field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
- }
-
- @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
- ctor @Deprecated public FingerprintManager.AuthenticationCallback();
- method @Deprecated public void onAuthenticationError(int, CharSequence);
- method @Deprecated public void onAuthenticationFailed();
- method @Deprecated public void onAuthenticationHelp(int, CharSequence);
- method @Deprecated public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
- }
-
- @Deprecated public static class FingerprintManager.AuthenticationResult {
- method @Deprecated public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
- }
-
- @Deprecated public static final class FingerprintManager.CryptoObject {
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
- ctor @Deprecated public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
- method @Deprecated public javax.crypto.Cipher getCipher();
- method @Deprecated public javax.crypto.Mac getMac();
- method @Deprecated public java.security.Signature getSignature();
- }
-
-}
-
package android.hardware.input {
public final class HostUsiVersion implements android.os.Parcelable {
@@ -41153,19 +41104,19 @@
method public final android.service.notification.StatusBarNotification[] getSnoozedNotifications();
method public final void migrateNotificationFilter(int, @Nullable java.util.List<java.lang.String>);
method public android.os.IBinder onBind(android.content.Intent);
- method public void onInterruptionFilterChanged(int);
- method public void onListenerConnected();
- method public void onListenerDisconnected();
- method public void onListenerHintsChanged(int);
- method public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
- method public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification);
- method public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
- method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
- method public void onSilentStatusBarIconsVisibilityChanged(boolean);
+ method @UiThread public void onInterruptionFilterChanged(int);
+ method @UiThread public void onListenerConnected();
+ method @UiThread public void onListenerDisconnected();
+ method @UiThread public void onListenerHintsChanged(int);
+ method @UiThread public void onNotificationChannelGroupModified(String, android.os.UserHandle, android.app.NotificationChannelGroup, int);
+ method @UiThread public void onNotificationChannelModified(String, android.os.UserHandle, android.app.NotificationChannel, int);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationPosted(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRankingUpdate(android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap);
+ method @UiThread public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, int);
+ method @UiThread public void onSilentStatusBarIconsVisibilityChanged(boolean);
method public final void requestInterruptionFilter(int);
method public final void requestListenerHints(int);
method public static void requestRebind(android.content.ComponentName);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9c1a8e8..0ab2588 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -652,6 +652,7 @@
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.webkit.WebViewProviderResponse> CREATOR;
field public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES = 4; // 0x4
+ field public static final int STATUS_FAILED_OTHER = 11; // 0xb
field public static final int STATUS_FAILED_WAITING_FOR_RELRO = 3; // 0x3
field public static final int STATUS_SUCCESS = 0; // 0x0
field @Nullable public final android.content.pm.PackageInfo packageInfo;
diff --git a/core/api/removed.txt b/core/api/removed.txt
index 3c7c0d6..c61f163 100644
--- a/core/api/removed.txt
+++ b/core/api/removed.txt
@@ -35,6 +35,7 @@
method @Deprecated @Nullable public String getFeatureId();
method public abstract android.content.SharedPreferences getSharedPreferences(java.io.File, int);
method public abstract java.io.File getSharedPreferencesPath(String);
+ field public static final String FINGERPRINT_SERVICE = "fingerprint";
}
public class ContextWrapper extends android.content.Context {
@@ -145,6 +146,54 @@
}
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected();
+ field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0
+ field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
+ field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2
+ field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1
+ field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5
+ field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5
+ field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc
+ field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1
+ field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7
+ field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9
+ field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb
+ field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4
+ field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3
+ field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2
+ field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa
+ field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8
+ }
+
+ @Deprecated public abstract static class FingerprintManager.AuthenticationCallback {
+ ctor public FingerprintManager.AuthenticationCallback();
+ method public void onAuthenticationError(int, CharSequence);
+ method public void onAuthenticationFailed();
+ method public void onAuthenticationHelp(int, CharSequence);
+ method public void onAuthenticationSucceeded(android.hardware.fingerprint.FingerprintManager.AuthenticationResult);
+ }
+
+ @Deprecated public static class FingerprintManager.AuthenticationResult {
+ method public android.hardware.fingerprint.FingerprintManager.CryptoObject getCryptoObject();
+ }
+
+ @Deprecated public static final class FingerprintManager.CryptoObject {
+ ctor public FingerprintManager.CryptoObject(@NonNull java.security.Signature);
+ ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Cipher);
+ ctor public FingerprintManager.CryptoObject(@NonNull javax.crypto.Mac);
+ method public javax.crypto.Cipher getCipher();
+ method public javax.crypto.Mac getMac();
+ method public java.security.Signature getSignature();
+ }
+
+}
+
package android.media {
public final class AudioFormat implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index afb796b..b73f199 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -70,7 +70,7 @@
field public static final String BIND_NETWORK_RECOMMENDATION_SERVICE = "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
field public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_INTELLIGENCE_SERVICE = "android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE";
- field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_TRUSTED_SERVICE = "android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE";
+ field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE = "android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE";
field public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE = "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
field public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
field public static final String BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE = "android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE";
@@ -403,7 +403,6 @@
field @Deprecated public static final String UPDATE_TIME_ZONE_RULES = "android.permission.UPDATE_TIME_ZONE_RULES";
field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
field public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String USE_BACKGROUND_FACE_AUTHENTICATION = "android.permission.USE_BACKGROUND_FACE_AUTHENTICATION";
field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
field @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public static final String USE_ON_DEVICE_INTELLIGENCE = "android.permission.USE_ON_DEVICE_INTELLIGENCE";
field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
@@ -2223,10 +2222,9 @@
}
public static final class Feature.Builder {
- ctor public Feature.Builder(int, int, int, @NonNull android.os.PersistableBundle);
+ ctor public Feature.Builder(int);
method @NonNull public android.app.ondeviceintelligence.Feature build();
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setFeatureParams(@NonNull android.os.PersistableBundle);
- method @NonNull public android.app.ondeviceintelligence.Feature.Builder setId(int);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setModelName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setName(@NonNull String);
method @NonNull public android.app.ondeviceintelligence.Feature.Builder setType(int);
@@ -2238,7 +2236,7 @@
ctor public FeatureDetails(@android.app.ondeviceintelligence.FeatureDetails.Status int);
method public int describeContents();
method @NonNull public android.os.PersistableBundle getFeatureDetailParams();
- method @android.app.ondeviceintelligence.FeatureDetails.Status public int getStatus();
+ method @android.app.ondeviceintelligence.FeatureDetails.Status public int getFeatureStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FeatureDetails> CREATOR;
field public static final int FEATURE_STATUS_AVAILABLE = 3; // 0x3
@@ -2251,27 +2249,16 @@
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.FIELD}) public static @interface FeatureDetails.Status {
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class FilePart implements android.os.Parcelable {
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull String) throws java.io.FileNotFoundException;
- ctor public FilePart(@NonNull String, @NonNull android.os.PersistableBundle, @NonNull java.io.FileInputStream) throws java.io.IOException;
- method public int describeContents();
- method @NonNull public java.io.FileInputStream getFileInputStream();
- method @NonNull public String getFilePartKey();
- method @NonNull public android.os.PersistableBundle getFilePartParams();
- method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.FilePart> CREATOR;
- }
-
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public class OnDeviceIntelligenceManager {
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeature(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method @Nullable @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public String getRemoteServicePackageName();
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void getVersion(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.LongConsumer);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void listFeatures(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequest(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+ method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenInfo(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2
field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0
field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1
@@ -2305,6 +2292,10 @@
field public static final int PROCESSING_ERROR_UNKNOWN = 1; // 0x1
}
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface ProcessingOutcomeReceiver extends android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ method public default void onDataAugmentRequest(@NonNull android.app.ondeviceintelligence.Content, @NonNull java.util.function.Consumer<android.app.ondeviceintelligence.Content>);
+ }
+
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class ProcessingSignal {
ctor public ProcessingSignal();
method public void sendSignal(@NonNull android.os.PersistableBundle);
@@ -2315,8 +2306,18 @@
method public void onSignalReceived(@NonNull android.os.PersistableBundle);
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamingResponseReceiver<R, T, E extends java.lang.Throwable> extends android.os.OutcomeReceiver<R,E> {
- method public void onNewContent(@NonNull T);
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public interface StreamedProcessingOutcomeReceiver extends android.app.ondeviceintelligence.ProcessingOutcomeReceiver {
+ method public void onNewContent(@NonNull android.app.ondeviceintelligence.Content);
+ }
+
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public final class TokenInfo implements android.os.Parcelable {
+ ctor public TokenInfo(long, @NonNull android.os.PersistableBundle);
+ ctor public TokenInfo(long);
+ method public int describeContents();
+ method public long getCount();
+ method @NonNull public android.os.PersistableBundle getInfoParams();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.ondeviceintelligence.TokenInfo> CREATOR;
}
}
@@ -3759,7 +3760,6 @@
field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
- field @FlaggedApi("android.hardware.biometrics.face_background_authentication") public static final String FACE_SERVICE = "face";
field public static final String FONT_SERVICE = "font";
field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final String MEDIA_TRANSCODING_SERVICE = "media_transcoding";
@@ -4386,7 +4386,7 @@
field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
- field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
+ field @Deprecated public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
field public static final int PROTECTION_FLAG_ROLE = 67108864; // 0x4000000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
@@ -4821,7 +4821,7 @@
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public void addSensorPrivacyListener(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.SensorPrivacyManager.OnSensorPrivacyChangedListener);
method @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean areAnySensorPrivacyTogglesEnabled(int);
- method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.Map<java.lang.String,java.lang.Boolean> getCameraPrivacyAllowlist();
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @NonNull @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public java.util.List<java.lang.String> getCameraPrivacyAllowlist();
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public int getSensorPrivacyState(int, int);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isCameraPrivacyEnabled(@NonNull String);
method @Deprecated @RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY) public boolean isSensorPrivacyEnabled(int);
@@ -4844,6 +4844,12 @@
method public boolean isEnabled();
}
+ @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") public static class SensorPrivacyManager.StateTypes {
+ field public static final int DISABLED = 2; // 0x2
+ field public static final int ENABLED = 1; // 0x1
+ field public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS = 3; // 0x3
+ }
+
}
package android.hardware.biometrics {
@@ -5131,15 +5137,6 @@
}
-package android.hardware.face {
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION) public void authenticateInBackground(@Nullable java.util.concurrent.Executor, @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject, @Nullable android.os.CancellationSignal, @NonNull android.hardware.biometrics.BiometricPrompt.AuthenticationCallback);
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @RequiresPermission(anyOf={"android.permission.USE_BIOMETRIC_INTERNAL", android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION}) public boolean hasEnrolledTemplates();
- }
-
-}
-
package android.hardware.hdmi {
public abstract class HdmiClient {
@@ -11531,7 +11528,7 @@
method public int checkPermissionForPreflight(@NonNull String, @NonNull android.content.AttributionSource);
method @RequiresPermission(value=android.Manifest.permission.UPDATE_APP_OPS_STATS, conditional=true) public int checkPermissionForStartDataDelivery(@NonNull String, @NonNull android.content.AttributionSource, @Nullable String);
method public void finishDataDelivery(@NonNull String, @NonNull android.content.AttributionSource);
- method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
+ method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public java.util.Map<java.lang.String,android.permission.PermissionManager.PermissionState> getAllPermissionStates(@NonNull String, @NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionGrantedPackages();
method @NonNull @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public java.util.Set<java.lang.String> getAutoRevokeExemptionRequestedPackages();
method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public int getPermissionFlags(@NonNull String, @NonNull String, @NonNull String);
@@ -12885,7 +12882,7 @@
}
public abstract class NotificationListenerService extends android.app.Service {
- method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
+ method @UiThread public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int);
}
public static class NotificationListenerService.Ranking {
@@ -12957,17 +12954,19 @@
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service {
ctor public OnDeviceIntelligenceService();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
- method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
- method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onDownloadFeature(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback);
+ method public abstract void onGetFeature(int, int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onGetFeatureDetails(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>);
method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer);
- method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
+ method public abstract void onInferenceServiceConnected();
+ method public abstract void onInferenceServiceDisconnected();
+ method public abstract void onListFeatures(int, @NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>);
method public final void updateProcessingState(@NonNull android.os.Bundle, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService";
}
- public static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
+ public abstract static class OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException extends java.lang.Exception {
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int);
ctor public OnDeviceIntelligenceService.OnDeviceIntelligenceServiceException(int, @NonNull String);
method public int getErrorCode();
@@ -12979,17 +12978,18 @@
field public static final int PROCESSING_UPDATE_STATUS_CONNECTION_FAILED = 1; // 0x1
}
- @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service {
- ctor public OnDeviceTrustedInferenceService();
+ @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceSandboxedInferenceService extends android.app.Service {
+ ctor public OnDeviceSandboxedInferenceService();
method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>);
+ method @NonNull public java.util.concurrent.Executor getCallbackExecutor();
method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
- method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
- method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
+ method @NonNull public abstract void onProcessRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.ProcessingOutcomeReceiver);
+ method @NonNull public abstract void onProcessRequestStreaming(int, @NonNull android.app.ondeviceintelligence.Feature, @Nullable android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver);
+ method @NonNull public abstract void onTokenInfoRequest(int, @NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.TokenInfo,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>);
method public abstract void onUpdateProcessingState(@NonNull android.os.Bundle, @NonNull android.os.OutcomeReceiver<android.os.PersistableBundle,android.service.ondeviceintelligence.OnDeviceIntelligenceService.OnDeviceUpdateProcessingException>);
method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException;
method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException;
- field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
}
}
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 1923641..1e72a06 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -511,9 +511,9 @@
InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#onBind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
-InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0:
+InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String) parameter #0:
Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0:
Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated.
@@ -571,7 +571,7 @@
Missing nullability on parameter `args` in method `dump`
MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
Missing nullability on parameter `base` in method `attachBaseContext`
-MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String):
+MissingNullability: android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService#openFileInput(String):
Missing nullability on method `openFileInput` return
MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
Missing nullability on parameter `intent` in method `onUnbind`
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a28dc49..15f1eb4 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1366,7 +1366,7 @@
}
@FlaggedApi("android.credentials.flags.configurable_selector_ui_enabled") public class IntentFactory {
- method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.os.IBinder, boolean, @NonNull String);
+ method @NonNull public static android.content.Intent createCancelUiIntent(@NonNull android.content.Context, @NonNull android.os.IBinder, boolean, @NonNull String);
method @NonNull public static android.content.Intent createCredentialSelectorIntent(@NonNull android.content.Context, @NonNull android.credentials.selection.RequestInfo, @NonNull java.util.ArrayList<android.credentials.selection.ProviderData>, @NonNull java.util.ArrayList<android.credentials.selection.DisabledProviderData>, @NonNull android.os.ResultReceiver);
}
@@ -1519,6 +1519,7 @@
package android.hardware {
public final class SensorPrivacyManager {
+ method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setCameraPrivacyAllowlist(@NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
method @FlaggedApi("com.android.internal.camera.flags.camera_privacy_allowlist") @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyState(int, int, int);
}
@@ -1547,10 +1548,6 @@
method public boolean isAllowBackgroundAuthentication();
}
- public abstract static class BiometricPrompt.AuthenticationCallback {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") public void onAuthenticationAcquired(int);
- }
-
public static class BiometricPrompt.Builder {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean);
method @FlaggedApi("android.multiuser.enable_biometrics_to_unlock_private_space") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.TEST_BIOMETRIC, "android.permission.USE_BIOMETRIC_INTERNAL"}) public android.hardware.biometrics.BiometricPrompt.Builder setAllowBackgroundAuthentication(boolean, boolean);
@@ -1569,7 +1566,6 @@
}
public class SensorProperties {
- ctor @FlaggedApi("android.hardware.biometrics.face_background_authentication") public SensorProperties(int, int, @NonNull java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo>);
method @NonNull public java.util.List<android.hardware.biometrics.SensorProperties.ComponentInfo> getComponentInfo();
method public int getSensorId();
method public int getSensorStrength();
@@ -1714,27 +1710,6 @@
}
-package android.hardware.face {
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceManager {
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
- method @FlaggedApi("android.hardware.biometrics.face_background_authentication") @NonNull public java.util.List<android.hardware.face.FaceSensorProperties> getSensorProperties();
- }
-
- @FlaggedApi("android.hardware.biometrics.face_background_authentication") public class FaceSensorProperties extends android.hardware.biometrics.SensorProperties {
- }
-
-}
-
-package android.hardware.fingerprint {
-
- @Deprecated public class FingerprintManager {
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
- method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
- }
-
-}
-
package android.hardware.hdmi {
public final class HdmiControlServiceWrapper {
@@ -3946,6 +3921,7 @@
}
public final class InputMethodInfo implements android.os.Parcelable {
+ ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, @NonNull String);
ctor @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, @NonNull String, boolean, boolean, @NonNull String);
ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int);
diff --git a/core/api/test-removed.txt b/core/api/test-removed.txt
index d802177..2e44176 100644
--- a/core/api/test-removed.txt
+++ b/core/api/test-removed.txt
@@ -1 +1,10 @@
// Signature format: 2.0
+package android.hardware.fingerprint {
+
+ @Deprecated public class FingerprintManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public android.hardware.biometrics.BiometricTestSession createTestSession(int);
+ method @NonNull @RequiresPermission(android.Manifest.permission.TEST_BIOMETRIC) public java.util.List<android.hardware.biometrics.SensorProperties> getSensorProperties();
+ }
+
+}
+
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 63cafdc..44dc8e2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1254,12 +1254,23 @@
return mApplication;
}
- /** Is this activity embedded inside of another activity? */
+ /**
+ * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
+ *
+ * @deprecated {@link ActivityGroup} is deprecated.
+ */
+ @Deprecated
public final boolean isChild() {
return mParent != null;
}
- /** Return the parent activity if this view is an embedded child. */
+ /**
+ * Returns the parent {@link Activity} if this is a child {@link Activity} of an
+ * {@link ActivityGroup}.
+ *
+ * @deprecated {@link ActivityGroup} is deprecated.
+ */
+ @Deprecated
public final Activity getParent() {
return mParent;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 41151c0..1d39186 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -40,6 +40,7 @@
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,6 +64,7 @@
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@
Configuration overrideConfig;
@NonNull
private ActivityWindowInfo mActivityWindowInfo;
+ @Nullable
+ private ActivityWindowInfo mLastReportedActivityWindowInfo;
// Used for consolidating configs before sending on to Activity.
private final Configuration tmpConfig = new Configuration();
@@ -4180,6 +4184,9 @@
pendingActions.setRestoreInstanceState(true);
pendingActions.setCallOnPostCreate(true);
}
+
+ // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
+ handleActivityWindowInfoChanged(r);
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -4558,7 +4565,7 @@
private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
final ClientTransaction transaction = ClientTransaction.obtain(mAppThread);
final PauseActivityItem pauseActivityItem = PauseActivityItem.obtain(r.token,
- r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
+ r.activity.isFinishing(), /* userLeaving */ true,
/* dontReport */ false, /* autoEnteringPip */ false);
transaction.addTransactionItem(pauseActivityItem);
executeTransaction(transaction);
@@ -5432,13 +5439,12 @@
@Override
public void handlePauseActivity(ActivityClientRecord r, boolean finished, boolean userLeaving,
- int configChanges, boolean autoEnteringPip, PendingTransactionActions pendingActions,
+ boolean autoEnteringPip, PendingTransactionActions pendingActions,
String reason) {
if (userLeaving) {
performUserLeavingActivity(r);
}
- r.activity.mConfigChangeFlags |= configChanges;
if (autoEnteringPip) {
// Set mIsInPictureInPictureMode earlier in case of auto-enter-pip, see also
// {@link Activity#enterPictureInPictureMode(PictureInPictureParams)}.
@@ -5687,9 +5693,8 @@
}
@Override
- public void handleStopActivity(ActivityClientRecord r, int configChanges,
+ public void handleStopActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
- r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
@@ -5859,11 +5864,10 @@
/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
- int configChanges, boolean getNonConfigInstance, String reason) {
+ boolean getNonConfigInstance, String reason) {
Class<? extends Activity> activityClass;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
activityClass = r.activity.getClass();
- r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
r.activity.mFinished = true;
}
@@ -5928,9 +5932,9 @@
}
@Override
- public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
+ public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
boolean getNonConfigInstance, String reason) {
- performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
+ performDestroyActivity(r, finishing, getNonConfigInstance, reason);
cleanUpPendingRemoveWindows(r, finishing);
WindowManager wm = r.activity.getWindowManager();
View v = r.activity.mDecor;
@@ -6130,7 +6134,7 @@
r.activity.mChangingConfigurations = true;
- handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+ handleRelaunchActivityInner(r, tmp.pendingResults, tmp.pendingIntents,
pendingActions, tmp.startsNotResumed, tmp.overrideConfig, tmp.mActivityWindowInfo,
"handleRelaunchActivity");
}
@@ -6199,7 +6203,7 @@
executeTransaction(transaction);
}
- private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r, int configChanges,
+ private void handleRelaunchActivityInner(@NonNull ActivityClientRecord r,
@Nullable List<ResultInfo> pendingResults,
@Nullable List<ReferrerIntent> pendingIntents,
@NonNull PendingTransactionActions pendingActions, boolean startsNotResumed,
@@ -6215,7 +6219,7 @@
callActivityOnStop(r, true /* saveState */, reason);
}
- handleDestroyActivity(r, false, configChanges, true, reason);
+ handleDestroyActivity(r, false /* finishing */, true /* getNonConfigInstance */, reason);
r.activity = null;
r.window = null;
@@ -6740,7 +6744,7 @@
// Perform updates.
r.overrideConfig = overrideConfig;
r.mActivityWindowInfo = activityWindowInfo;
- // TODO(b/287582673): notify on ActivityWindowInfo change
+
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
@@ -6763,6 +6767,22 @@
viewRoot.updateConfiguration(displayId);
}
mSomeActivitiesChanged = true;
+
+ // Trigger ActivityWindowInfo callback if changed.
+ handleActivityWindowInfoChanged(r);
+ }
+
+ private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ if (r.mActivityWindowInfo == null
+ || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
+ return;
+ }
+ r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
+ ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
+ r.mActivityWindowInfo);
}
final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index f6ec370..5e2397d 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -173,8 +173,8 @@
* interrupt the user (e.g. via sound & vibration) while this rule
* is active.
* @param enabled Whether the rule is enabled.
- * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
- * ZenPolicy, int, boolean)}.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
@Deprecated
public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
@@ -206,8 +206,10 @@
* while this rule is active. This overrides the global policy while this rule is
* action ({@link Condition#STATE_TRUE}).
* @param enabled Whether the rule is enabled.
+ *
+ * @deprecated Use {@link AutomaticZenRule.Builder} to construct an {@link AutomaticZenRule}.
*/
- // TODO (b/309088420): deprecate this constructor in favor of the builder
+ @Deprecated
public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
@Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
@Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
@@ -368,6 +370,9 @@
/**
* Sets the zen policy.
+ *
+ * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
+ * a {@code null} value here means the previous policy is retained.
*/
public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
@@ -390,7 +395,12 @@
* Sets the configuration activity - an activity that handles
* {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
* about this rule and/or allows them to configure it. This is required to be non-null for rules
- * that are not backed by {@link android.service.notification.ConditionProviderService}.
+ * that are not backed by a {@link android.service.notification.ConditionProviderService}.
+ *
+ * <p>This is exclusive with the {@code owner} supplied in the constructor; rules where a
+ * configuration activity is set will not use the
+ * {@link android.service.notification.ConditionProviderService} supplied there to determine
+ * whether the rule should be active.
*/
public void setConfigurationActivity(@Nullable ComponentName componentName) {
this.configurationActivity = getTrimmedComponentName(componentName);
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index b5b3669..01153c9 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -114,11 +114,11 @@
/** Destroy the activity. */
public abstract void handleDestroyActivity(@NonNull ActivityClientRecord r, boolean finishing,
- int configChanges, boolean getNonConfigInstance, String reason);
+ boolean getNonConfigInstance, String reason);
/** Pause the activity. */
public abstract void handlePauseActivity(@NonNull ActivityClientRecord r, boolean finished,
- boolean userLeaving, int configChanges, boolean autoEnteringPip,
+ boolean userLeaving, boolean autoEnteringPip,
PendingTransactionActions pendingActions, String reason);
/**
@@ -146,14 +146,13 @@
/**
* Stop the activity.
* @param r Target activity record.
- * @param configChanges Activity configuration changes.
* @param pendingActions Pending actions to be used on this or later stages of activity
* transaction.
* @param finalStateRequest Flag indicating if this call is handling final lifecycle state
* request for a transaction.
* @param reason Reason for performing this operation.
*/
- public abstract void handleStopActivity(@NonNull ActivityClientRecord r, int configChanges,
+ public abstract void handleStopActivity(@NonNull ActivityClientRecord r,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
/** Report that activity was stopped to server. */
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index 483a6e1..4ce983f 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -16,8 +16,10 @@
package android.app;
+import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.content.Context;
import android.content.res.Configuration;
@@ -127,6 +129,7 @@
*
* @see Configuration#getGrammaticalGender
*/
+ @RequiresPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
@FlaggedApi(Flags.FLAG_SYSTEM_TERMS_OF_ADDRESS_ENABLED)
@Configuration.GrammaticalGender
public int getSystemGrammaticalGender() {
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 1b19ecd..095cfc5 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -413,7 +413,7 @@
if (localLOGV) Log.v(TAG, r.id + ": destroying");
final ActivityClientRecord clientRecord = mActivityThread.getActivityClient(r);
if (clientRecord != null) {
- mActivityThread.performDestroyActivity(clientRecord, finish, 0 /* configChanges */,
+ mActivityThread.performDestroyActivity(clientRecord, finish,
false /* getNonConfigInstance */, "LocalActivityManager::performDestroy");
}
r.activity = null;
@@ -684,7 +684,7 @@
if (localLOGV) Log.v(TAG, r.id + ": no corresponding record");
continue;
}
- mActivityThread.performDestroyActivity(clientRecord, finishing, 0 /* configChanges */,
+ mActivityThread.performDestroyActivity(clientRecord, finishing,
false /* getNonConfigInstance */, "LocalActivityManager::dispatchDestroy");
}
mActivities.clear();
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index b2be27f..4a06f7d 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -248,7 +248,8 @@
}
/**
- * Returns the default locale if specified, otherwise null
+ * Returns the locale the strings in values/strings.xml (the default strings in the directory
+ * with no locale qualifier) are in if specified, otherwise null
*
* @return The default Locale or null
*/
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 1129f9d..79cb09d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6068,11 +6068,19 @@
}
/**
- * Construct a RemoteViews for the final 1U notification layout. In order:
- * 1. Custom contentView from the caller
- * 2. Style's proposed content view
- * 3. Standard template view
+ * Construct a RemoteViews representing the standard notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createContentView() {
return createContentView(false /* increasedheight */ );
}
@@ -6181,8 +6189,19 @@
}
/**
- * Construct a RemoteViews for the final big notification layout.
+ * Construct a RemoteViews representing the expanded notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createBigContentView() {
RemoteViews result = null;
if (useExistingRemoteView(mN.bigContentView)) {
@@ -6315,8 +6334,19 @@
}
/**
- * Construct a RemoteViews for the final heads-up notification layout.
+ * Construct a RemoteViews representing the heads up notification layout.
+ *
+ * @deprecated For performance and system health reasons, this API is no longer required to
+ * be used directly by the System UI when rendering Notifications to the user. While the UI
+ * returned by this method will still represent the content of the Notification being
+ * built, it may differ from the visual style of the system.
+ *
+ * NOTE: this API has always had severe limitations; for example it does not support any
+ * interactivity, it ignores the app theme, it hard-codes the colors from the system theme
+ * at the time it is called, and it does Bitmap decoding on the main thread which can cause
+ * UI jank.
*/
+ @Deprecated
public RemoteViews createHeadsUpContentView() {
return createHeadsUpContentView(false /* useIncreasedHeight */);
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index ab5395e..9f2e473 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -562,6 +562,12 @@
* audio attributes. Notification channels with an {@link #getImportance() importance} of at
* least {@link NotificationManager#IMPORTANCE_DEFAULT} should have a sound.
*
+ * Note: An app-specific sound can be provided in the Uri parameter, but because channels are
+ * persistent for the duration of the app install, and are backed up and restored, the Uri
+ * should be stable. For this reason it is not recommended to use a
+ * {@link ContentResolver#SCHEME_ANDROID_RESOURCE} uri, as resource ids can change on app
+ * upgrade.
+ *
* Only modifiable before the channel is submitted to
* {@link NotificationManager#createNotificationChannel(NotificationChannel)}.
*/
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d49a254..b82a1e3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -270,13 +270,16 @@
* Integer extra for {@link #ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED} containing the state of
* the {@link AutomaticZenRule}.
*
- * <p>
- * The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
- * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
- * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
- * </p>
+ * <p>The value will be one of {@link #AUTOMATIC_RULE_STATUS_ENABLED},
+ * {@link #AUTOMATIC_RULE_STATUS_DISABLED}, {@link #AUTOMATIC_RULE_STATUS_REMOVED},
+ * {@link #AUTOMATIC_RULE_STATUS_ACTIVATED}, {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED}, or
+ * {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
+ *
+ * <p>Note that the {@link #AUTOMATIC_RULE_STATUS_ACTIVATED} and
+ * {@link #AUTOMATIC_RULE_STATUS_DEACTIVATED} statuses are only sent to packages targeting
+ * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above; apps targeting a lower SDK version
+ * will be sent {@link #AUTOMATIC_RULE_STATUS_UNKNOWN} in their place instead.
*/
- // TODO (b/309101513): Add new status types to javadoc
public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
"android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";
@@ -370,11 +373,15 @@
= "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
/**
- * Intent that is broadcast when the state of getNotificationPolicy() changes.
+ * Intent that is broadcast when the state of {@link #getNotificationPolicy()} changes.
*
* <p>This broadcast is only sent to registered receivers and (starting from
* {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not
* Disturb access (see {@link #isNotificationPolicyAccessGranted()}).
+ *
+ * <p>Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, most calls to
+ * {@link #setNotificationPolicy(Policy)} will update the app's implicit rule policy instead of
+ * the global policy, so this broadcast will be sent much less frequently.
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_NOTIFICATION_POLICY_CHANGED
@@ -1378,12 +1385,16 @@
/**
* Updates the given zen rule.
*
- * <p>
- * Throws a SecurityException if policy access is not granted to this package.
+ * <p>Before {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, updating a rule that is not backed
+ * up by a {@link android.service.notification.ConditionProviderService} will deactivate it if
+ * it was previously active. Starting with {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, this
+ * will only happen if the rule's definition is actually changing.
+ *
+ * <p>Throws a SecurityException if policy access is not granted to this package.
* See {@link #isNotificationPolicyAccessGranted}.
*
- * <p>
- * Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ * <p>Callers can only update rules that they own. See {@link AutomaticZenRule#getOwner}.
+ *
* @param id The id of the rule to update
* @param automaticZenRule the rule to update.
* @return Whether the rule was successfully updated.
@@ -1744,9 +1755,11 @@
/**
* Gets the current user-specified default notification policy.
*
- * <p>
+ * <p>For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) this method will return the policy associated
+ * to their implicit {@link AutomaticZenRule} instead, if it exists. See
+ * {@link #setNotificationPolicy(Policy)}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public Policy getNotificationPolicy() {
INotificationManager service = getService();
try {
@@ -1757,15 +1770,20 @@
}
/**
- * Sets the current notification policy.
+ * Sets the current notification policy (which applies when {@link #setInterruptionFilter} is
+ * called with the {@link #INTERRUPTION_FILTER_PRIORITY} value).
*
- * <p>
- * Only available if policy access is granted to this package.
- * See {@link #isNotificationPolicyAccessGranted}.
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global notification policy.
+ * Calling this method will instead create or update an {@link AutomaticZenRule} associated to
+ * the app, using a {@link ZenPolicy} corresponding to the {@link Policy} supplied here, and
+ * which will be activated/deactivated by calls to {@link #setInterruptionFilter(int)}.
+ *
+ * <p>Only available if policy access is granted to this package. See
+ * {@link #isNotificationPolicyAccessGranted}.
*
* @param policy The new desired policy.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
setNotificationPolicy(policy, /* fromUser= */ false);
}
@@ -2052,10 +2070,12 @@
/** Notification senders to prioritize for calls. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityCallSenders;
/** Notification senders to prioritize for messages. One of:
* PRIORITY_SENDERS_ANY, PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED */
+ @PrioritySenders
public final int priorityMessageSenders;
/**
@@ -2063,6 +2083,7 @@
* {@link #CONVERSATION_SENDERS_NONE}, {@link #CONVERSATION_SENDERS_IMPORTANT},
* {@link #CONVERSATION_SENDERS_ANYONE}.
*/
+ @ConversationSenders
public final int priorityConversationSenders;
/**
@@ -2630,16 +2651,19 @@
}
/** @hide **/
+ @PrioritySenders
public int allowCallsFrom() {
return priorityCallSenders;
}
/** @hide **/
+ @PrioritySenders
public int allowMessagesFrom() {
return priorityMessageSenders;
}
/** @hide **/
+ @ConversationSenders
public int allowConversationsFrom() {
return priorityConversationSenders;
}
@@ -2780,11 +2804,17 @@
* The interruption filter defines which notifications are allowed to
* interrupt the user (e.g. via sound & vibration) and is applied
* globally.
- * <p>
- * Only available if policy access is granted to this package. See
+ *
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an {@link AutomaticZenRule}
+ * associated to the app, using a {@link ZenPolicy} that corresponds to the {@link Policy}
+ * supplied to {@link #setNotificationPolicy(Policy)} (or the global policy when one wasn't
+ * provided).
+ *
+ * <p> Only available if policy access is granted to this package. See
* {@link #isNotificationPolicyAccessGranted}.
*/
- // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
setInterruptionFilter(interruptionFilter, /* fromUser= */ false);
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 6255260..8b84f06 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -124,6 +124,32 @@
*/
private LocaleConfig mLocaleConfig = new LocaleConfig(LocaleList.getEmptyLocaleList());
+ private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap =
+ new ArrayMap<>();
+
+ /**
+ * The internal function to register the resources paths of a package (e.g. a shared library).
+ * This will collect the package resources' paths from its ApplicationInfo and add them to all
+ * existing and future contexts while the application is running.
+ */
+ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) {
+ SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir,
+ appInfo.splitSourceDirs, appInfo.sharedLibraryFiles,
+ appInfo.resourceDirs, appInfo.overlayPaths);
+
+ synchronized (mLock) {
+ if (mSharedLibAssetsMap.containsKey(uniqueId)) {
+ Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId
+ + " has already been registered, this is a no-op.");
+ return;
+ }
+ mSharedLibAssetsMap.put(uniqueId, sharedLibAssets);
+ appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths());
+ Slog.v(TAG, "The following resources' paths have been added: "
+ + Arrays.toString(sharedLibAssets.getAllAssetPaths()));
+ }
+ }
+
private static class ApkKey {
public final String path;
public final boolean sharedLib;
@@ -278,6 +304,21 @@
public ResourcesManager() {
}
+ /**
+ * Inject a customized ResourcesManager instance for testing, return the old ResourcesManager
+ * instance.
+ */
+ @UnsupportedAppUsage
+ @VisibleForTesting
+ public static ResourcesManager setInstance(ResourcesManager resourcesManager) {
+ synchronized (ResourcesManager.class) {
+ ResourcesManager oldResourceManager = sResourcesManager;
+ sResourcesManager = resourcesManager;
+ return oldResourceManager;
+ }
+
+ }
+
@UnsupportedAppUsage
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -1480,6 +1521,56 @@
}
}
+ private void appendLibAssetsLocked(String[] libAssets) {
+ synchronized (mLock) {
+ // Record which ResourcesImpl need updating
+ // (and what ResourcesKey they should update to).
+ final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>();
+
+ final int implCount = mResourceImpls.size();
+ for (int i = 0; i < implCount; i++) {
+ final ResourcesKey key = mResourceImpls.keyAt(i);
+ final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
+ final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
+ if (impl == null) {
+ Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to "
+ + "append shared library assets for next ResourcesImpl.");
+ continue;
+ }
+
+ var newDirs = new ArrayList<String>();
+ var dirsSet = new ArraySet<String>();
+ if (key.mLibDirs != null) {
+ final int dirsLength = key.mLibDirs.length;
+ for (int k = 0; k < dirsLength; k++) {
+ newDirs.add(key.mLibDirs[k]);
+ dirsSet.add(key.mLibDirs[k]);
+ }
+ }
+ final int assetsLength = libAssets.length;
+ for (int j = 0; j < assetsLength; j++) {
+ if (dirsSet.add(libAssets[j])) {
+ newDirs.add(libAssets[j]);
+ }
+ }
+ String[] newLibAssets = newDirs.toArray(new String[0]);
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(impl, new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayPaths,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo,
+ key.mLoaders));
+ }
+ }
+
+ redirectResourcesToNewImplLocked(updatedResourceKeys);
+ }
+ }
+
private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
@NonNull final ApplicationInfo appInfo) {
try {
@@ -1689,4 +1780,50 @@
}
}
}
+
+ public static class SharedLibraryAssets{
+ private final String[] mAssetPaths;
+
+ SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles,
+ String[] resourceDirs, String[] overlayPaths) {
+ mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles,
+ resourceDirs, overlayPaths);
+ }
+
+ private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs,
+ String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) {
+ final String[][] inputLists = {
+ splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths
+ };
+
+ final ArraySet<String> assetPathSet = new ArraySet<>();
+ final List<String> assetPathList = new ArrayList<>();
+ if (sourceDir != null) {
+ assetPathSet.add(sourceDir);
+ assetPathList.add(sourceDir);
+ }
+
+ for (int i = 0; i < inputLists.length; i++) {
+ if (inputLists[i] != null) {
+ for (int j = 0; j < inputLists[i].length; j++) {
+ if (assetPathSet.add(inputLists[i][j])) {
+ assetPathList.add(inputLists[i][j]);
+ }
+ }
+ }
+ }
+ return assetPathList.toArray(new String[0]);
+ }
+
+ /**
+ * @return all the asset paths of this collected in this class.
+ */
+ public @NonNull String[] getAllAssetPaths() {
+ return mAssetPaths;
+ }
+ }
+
+ public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() {
+ return new ArrayMap<>(mSharedLibAssetsMap);
+ }
}
diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java
index d81eb20..51f3137 100644
--- a/core/java/android/app/admin/AccountTypePolicyKey.java
+++ b/core/java/android/app/admin/AccountTypePolicyKey.java
@@ -19,12 +19,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_ACCOUNT_TYPE;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -54,7 +54,7 @@
@TestApi
public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) {
super(key);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "accountType");
}
mAccountType = Objects.requireNonNull((accountType));
diff --git a/core/java/android/app/admin/BundlePolicyValue.java b/core/java/android/app/admin/BundlePolicyValue.java
index cc5e75f..cb5e986 100644
--- a/core/java/android/app/admin/BundlePolicyValue.java
+++ b/core/java/android/app/admin/BundlePolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -32,8 +31,8 @@
public BundlePolicyValue(Bundle value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(value);
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
+ PolicySizeVerifier.enforceMaxBundleFieldsLength(value);
}
}
diff --git a/core/java/android/app/admin/ComponentNamePolicyValue.java b/core/java/android/app/admin/ComponentNamePolicyValue.java
index 4d36195..a957dbf 100644
--- a/core/java/android/app/admin/ComponentNamePolicyValue.java
+++ b/core/java/android/app/admin/ComponentNamePolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.content.ComponentName;
import android.os.Parcel;
@@ -32,7 +31,7 @@
public ComponentNamePolicyValue(@NonNull ComponentName value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxComponentNameLength(value);
}
}
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index de7ff9f..7526a7b 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -19,7 +19,6 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_INTENT_FILTER;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,9 +59,6 @@
@TestApi
public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
- PolicySizeVerifier.enforceMaxParcelableFieldsLength(filter);
- }
mFilter = Objects.requireNonNull(filter);
}
diff --git a/core/java/android/app/admin/LockTaskPolicy.java b/core/java/android/app/admin/LockTaskPolicy.java
index 9d6ce24..a36ea05 100644
--- a/core/java/android/app/admin/LockTaskPolicy.java
+++ b/core/java/android/app/admin/LockTaskPolicy.java
@@ -16,11 +16,10 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -136,7 +135,7 @@
}
private void setPackagesInternal(Set<String> packages) {
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String p : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(p);
}
diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java
index 2241fdd..389585f 100644
--- a/core/java/android/app/admin/PackagePermissionPolicyKey.java
+++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java
@@ -20,12 +20,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_PERMISSION_NAME;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -59,7 +59,7 @@
public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName,
@NonNull String permissionName) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
PolicySizeVerifier.enforceMaxStringLength(permissionName, "permissionName");
}
diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java
index 2ea17a1..68dc797 100644
--- a/core/java/android/app/admin/PackagePolicyKey.java
+++ b/core/java/android/app/admin/PackagePolicyKey.java
@@ -19,12 +19,12 @@
import static android.app.admin.PolicyUpdateReceiver.EXTRA_PACKAGE_NAME;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_BUNDLE_KEY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -55,7 +55,7 @@
@TestApi
public PackagePolicyKey(@NonNull String key, @NonNull String packageName) {
super(key);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxPackageNameLength(packageName);
}
mPackageName = Objects.requireNonNull((packageName));
diff --git a/core/java/android/app/admin/PolicySizeVerifier.java b/core/java/android/app/admin/PolicySizeVerifier.java
index 792ebc6..7f8e50e 100644
--- a/core/java/android/app/admin/PolicySizeVerifier.java
+++ b/core/java/android/app/admin/PolicySizeVerifier.java
@@ -17,12 +17,12 @@
package android.app.admin;
import android.content.ComponentName;
+import android.os.Bundle;
import android.os.Parcelable;
import android.os.PersistableBundle;
import com.android.internal.util.Preconditions;
-import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -71,44 +71,51 @@
for (String key : current.keySet()) {
enforceMaxStringLength(key, "key in " + argName);
Object value = current.get(key);
- if (value instanceof String) {
- enforceMaxStringLength((String) value, "string value in " + argName);
- } else if (value instanceof String[]) {
- for (String str : (String[]) value) {
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in " + argName);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
enforceMaxStringLength(str, "string value in " + argName);
}
- } else if (value instanceof PersistableBundle) {
- queue.add((PersistableBundle) value);
+ } else if (value instanceof PersistableBundle persistableBundle) {
+ queue.add(persistableBundle);
}
}
}
}
/**
- * Throw if Parcelable contains any string that's too long to be serialized.
+ * Throw if bundle contains any string that's too long to be serialized. This follows the
+ * serialization logic in BundlePolicySerializer#writeBundle.
*/
- public static void enforceMaxParcelableFieldsLength(Parcelable parcelable) {
- // TODO(b/326662716) rework to protect against infinite recursion.
- if (true) {
- return;
- }
- Class<?> clazz = parcelable.getClass();
-
- Field[] fields = clazz.getDeclaredFields();
- for (Field field : fields) {
- field.setAccessible(true);
- try {
- Object value = field.get(parcelable);
- if (value instanceof String) {
- String stringValue = (String) value;
- enforceMaxStringLength(stringValue, field.getName());
+ public static void enforceMaxBundleFieldsLength(Bundle bundle) {
+ Queue<Bundle> queue = new ArrayDeque<>();
+ queue.add(bundle);
+ while (!queue.isEmpty()) {
+ Bundle current = queue.remove();
+ for (String key : current.keySet()) {
+ enforceMaxStringLength(key, "key in Bundle");
+ Object value = current.get(key);
+ if (value instanceof String str) {
+ enforceMaxStringLength(str, "string value in Bundle with "
+ + "key" + key);
+ } else if (value instanceof String[] strArray) {
+ for (String str : strArray) {
+ enforceMaxStringLength(str, "string value in Bundle with"
+ + " key" + key);
+ }
+ } else if (value instanceof Bundle b) {
+ queue.add(b);
}
-
- if (value instanceof Parcelable) {
- enforceMaxParcelableFieldsLength((Parcelable) value);
+ else if (value instanceof Parcelable[] parcelableArray) {
+ for (Parcelable parcelable : parcelableArray) {
+ if (!(parcelable instanceof Bundle)) {
+ throw new IllegalArgumentException("bundle-array can only hold "
+ + "Bundles");
+ }
+ queue.add((Bundle) parcelable);
+ }
}
- } catch (IllegalAccessException e) {
- e.printStackTrace();
}
}
}
diff --git a/core/java/android/app/admin/StringPolicyValue.java b/core/java/android/app/admin/StringPolicyValue.java
index f4d4adc..8995c0f 100644
--- a/core/java/android/app/admin/StringPolicyValue.java
+++ b/core/java/android/app/admin/StringPolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import java.util.Objects;
@@ -31,7 +30,7 @@
public StringPolicyValue(@NonNull String value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(value, "policyValue");
}
}
diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/StringSetPolicyValue.java
index 82fe761..f37dfee 100644
--- a/core/java/android/app/admin/StringSetPolicyValue.java
+++ b/core/java/android/app/admin/StringSetPolicyValue.java
@@ -16,10 +16,9 @@
package android.app.admin;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.flags.Flags;
import android.os.Parcel;
import java.util.HashSet;
@@ -33,7 +32,7 @@
public StringSetPolicyValue(@NonNull Set<String> value) {
super(value);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String str : value) {
PolicySizeVerifier.enforceMaxStringLength(str, "policyValue");
}
diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java
index d69a5f0..ee90ccd 100644
--- a/core/java/android/app/admin/UserRestrictionPolicyKey.java
+++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java
@@ -17,11 +17,11 @@
package android.app.admin;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY;
-import static android.app.admin.flags.Flags.devicePolicySizeTrackingEnabled;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.admin.flags.Flags;
import android.os.Bundle;
import android.os.Parcel;
@@ -45,7 +45,7 @@
@TestApi
public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) {
super(identifier);
- if (devicePolicySizeTrackingEnabled()) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(restriction, "restriction");
}
mRestriction = Objects.requireNonNull(restriction);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index e1a6913..1927019 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -10,7 +10,14 @@
flag {
name: "device_policy_size_tracking_enabled"
namespace: "enterprise"
- description: "Add feature to track the total policy size and have a max threshold."
+ description: "Add feature to track the total policy size and have a max threshold - public API changes"
+ bug: "281543351"
+}
+
+flag {
+ name: "device_policy_size_tracking_internal_enabled"
+ namespace: "enterprise"
+ description: "Add feature to track the total policy size and have a max threshold - internal changes"
bug: "281543351"
}
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index fb1b17b..89199ca 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -1,5 +1,7 @@
package android.app.assist;
+import static android.credentials.Constants.FAILURE_CREDMAN_SELECTOR;
+import static android.credentials.Constants.SUCCESS_CREDMAN_SELECTOR;
import static android.service.autofill.Flags.FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION;
import android.annotation.FlaggedApi;
@@ -20,14 +22,17 @@
import android.os.BadParcelableException;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
+import android.os.Looper;
import android.os.OutcomeReceiver;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PooledStringReader;
import android.os.PooledStringWriter;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.SystemClock;
import android.service.autofill.FillRequest;
import android.service.credentials.CredentialProviderService;
@@ -37,6 +42,7 @@
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.view.View;
import android.view.View.AutofillImportance;
import android.view.ViewRootImpl;
@@ -652,6 +658,9 @@
@Nullable OutcomeReceiver<GetCredentialResponse, GetCredentialException>
mGetCredentialCallback;
+ @Nullable ResultReceiver mGetCredentialResultReceiver;
+
+
AutofillValue mAutofillValue;
CharSequence[] mAutofillOptions;
boolean mSanitized;
@@ -916,6 +925,7 @@
mExtras = in.readBundle();
}
mGetCredentialRequest = in.readTypedObject(GetCredentialRequest.CREATOR);
+ mGetCredentialResultReceiver = in.readTypedObject(ResultReceiver.CREATOR);
}
/**
@@ -1153,6 +1163,7 @@
out.writeBundle(mExtras);
}
out.writeTypedObject(mGetCredentialRequest, flags);
+ out.writeTypedObject(mGetCredentialResultReceiver, flags);
return flags;
}
@@ -1295,9 +1306,8 @@
*/
@FlaggedApi(FLAG_AUTOFILL_CREDMAN_DEV_INTEGRATION)
@Nullable
- public OutcomeReceiver<GetCredentialResponse,
- GetCredentialException> getCredentialManagerCallback() {
- return mGetCredentialCallback;
+ public ResultReceiver getCredentialManagerCallback() {
+ return mGetCredentialResultReceiver;
}
/**
@@ -1894,6 +1904,7 @@
final AssistStructure mAssist;
final ViewNode mNode;
final boolean mAsync;
+ private Handler mHandler;
/**
* Used to instantiate a builder for a stand-alone {@link ViewNode} which is not associated
@@ -2271,6 +2282,56 @@
option.getCandidateQueryData()
.putParcelableArrayList(CredentialProviderService.EXTRA_AUTOFILL_ID, ids);
}
+ setUpResultReceiver(callback);
+ }
+
+ private void setUpResultReceiver(
+ OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
+
+ if (mHandler == null) {
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+ final ResultReceiver resultReceiver = new ResultReceiver(mHandler) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ if (resultCode == SUCCESS_CREDMAN_SELECTOR) {
+ Slog.d(TAG, "onReceiveResult from Credential Manager");
+ GetCredentialResponse getCredentialResponse =
+ resultData.getParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ GetCredentialResponse.class);
+
+ callback.onResult(getCredentialResponse);
+ } else if (resultCode == FAILURE_CREDMAN_SELECTOR) {
+ String[] exception = resultData.getStringArray(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION);
+ if (exception != null && exception.length >= 2) {
+ Slog.w(TAG, "Credman bottom sheet from pinned "
+ + "entry failed with: + " + exception[0] + " , "
+ + exception[1]);
+ callback.onError(new GetCredentialException(
+ exception[0], exception[1]));
+ }
+ } else {
+ Slog.d(TAG, "Unknown resultCode from credential "
+ + "manager bottom sheet: " + resultCode);
+ }
+ }
+ };
+ ResultReceiver ipcFriendlyResultReceiver =
+ toIpcFriendlyResultReceiver(resultReceiver);
+ mNode.mGetCredentialResultReceiver = ipcFriendlyResultReceiver;
+ }
+
+ private ResultReceiver toIpcFriendlyResultReceiver(ResultReceiver resultReceiver) {
+ final Parcel parcel = Parcel.obtain();
+ resultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ final ResultReceiver ipcFriendly = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+
+ return ipcFriendly;
}
@Override
diff --git a/core/java/android/app/ondeviceintelligence/Feature.java b/core/java/android/app/ondeviceintelligence/Feature.java
index 5107354..4a38c92 100644
--- a/core/java/android/app/ondeviceintelligence/Feature.java
+++ b/core/java/android/app/ondeviceintelligence/Feature.java
@@ -199,24 +199,13 @@
private long mBuilderFieldsSet = 0L;
- public Builder(
- int id,
- int type,
- int variant,
- @NonNull PersistableBundle featureParams) {
+ /**
+ * Provides a builder instance to create a feature for given id.
+ * @param id the unique identifier for the feature.
+ */
+ public Builder(int id) {
mId = id;
- mType = type;
- mVariant = variant;
- mFeatureParams = featureParams;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mFeatureParams);
- }
-
- public @NonNull Builder setId(int value) {
- checkNotUsed();
- mBuilderFieldsSet |= 0x1;
- mId = value;
- return this;
+ mFeatureParams = new PersistableBundle();
}
public @NonNull Builder setName(@NonNull String value) {
diff --git a/core/java/android/app/ondeviceintelligence/FeatureDetails.java b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
index 92f3513..f3cbd26 100644
--- a/core/java/android/app/ondeviceintelligence/FeatureDetails.java
+++ b/core/java/android/app/ondeviceintelligence/FeatureDetails.java
@@ -41,7 +41,7 @@
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public final class FeatureDetails implements Parcelable {
@Status
- private final int mStatus;
+ private final int mFeatureStatus;
@NonNull
private final PersistableBundle mFeatureDetailParams;
@@ -73,21 +73,21 @@
}
public FeatureDetails(
- @Status int status,
+ @Status int featureStatus,
@NonNull PersistableBundle featureDetailParams) {
- this.mStatus = status;
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = featureDetailParams;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
}
public FeatureDetails(
- @Status int status) {
- this.mStatus = status;
+ @Status int featureStatus) {
+ this.mFeatureStatus = featureStatus;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = new PersistableBundle();
}
@@ -95,8 +95,8 @@
/**
* Returns an integer value associated with the feature status.
*/
- public @Status int getStatus() {
- return mStatus;
+ public @Status int getFeatureStatus() {
+ return mFeatureStatus;
}
@@ -111,7 +111,7 @@
public String toString() {
return MessageFormat.format("FeatureDetails '{' status = {0}, "
+ "persistableBundle = {1} '}'",
- mStatus,
+ mFeatureStatus,
mFeatureDetailParams);
}
@@ -121,21 +121,21 @@
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
FeatureDetails that = (FeatureDetails) o;
- return mStatus == that.mStatus
+ return mFeatureStatus == that.mFeatureStatus
&& java.util.Objects.equals(mFeatureDetailParams, that.mFeatureDetailParams);
}
@Override
public int hashCode() {
int _hash = 1;
- _hash = 31 * _hash + mStatus;
+ _hash = 31 * _hash + mFeatureStatus;
_hash = 31 * _hash + java.util.Objects.hashCode(mFeatureDetailParams);
return _hash;
}
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
- dest.writeInt(mStatus);
+ dest.writeInt(mFeatureStatus);
dest.writeTypedObject(mFeatureDetailParams, flags);
}
@@ -151,9 +151,9 @@
PersistableBundle persistableBundle = (PersistableBundle) in.readTypedObject(
PersistableBundle.CREATOR);
- this.mStatus = status;
+ this.mFeatureStatus = status;
com.android.internal.util.AnnotationValidations.validate(
- Status.class, null, mStatus);
+ Status.class, null, mFeatureStatus);
this.mFeatureDetailParams = persistableBundle;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mFeatureDetailParams);
diff --git a/core/java/android/app/ondeviceintelligence/FilePart.java b/core/java/android/app/ondeviceintelligence/FilePart.java
deleted file mode 100644
index e9fb5f2..0000000
--- a/core/java/android/app/ondeviceintelligence/FilePart.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.ondeviceintelligence;
-
-import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
-
-import android.annotation.FlaggedApi;
-import android.annotation.SystemApi;
-import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.os.Parcelable;
-import android.os.PersistableBundle;
-
-import android.annotation.NonNull;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Objects;
-
-/**
- * Represents file data with an associated file descriptor sent to and received from remote
- * processing. The interface ensures that the underlying file-descriptor is always opened in
- * read-only mode.
- *
- * @hide
- */
-@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-@SystemApi
-public final class FilePart implements Parcelable {
- private final String mPartKey;
- private final PersistableBundle mPartParams;
- private final ParcelFileDescriptor mParcelFileDescriptor;
-
- private FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull ParcelFileDescriptor parcelFileDescriptor) {
- Objects.requireNonNull(partKey);
- Objects.requireNonNull(partParams);
- this.mPartKey = partKey;
- this.mPartParams = partParams;
- this.mParcelFileDescriptor = Objects.requireNonNull(parcelFileDescriptor);
- }
-
- /**
- * Create a file part using a filePath and any additional params.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull String filePath)
- throws FileNotFoundException {
- this(partKey, partParams, Objects.requireNonNull(ParcelFileDescriptor.open(
- new File(Objects.requireNonNull(filePath)), ParcelFileDescriptor.MODE_READ_ONLY)));
- }
-
- /**
- * Create a file part using a file input stream and any additional params.
- * It is the caller's responsibility to close the stream. It is safe to do so as soon as this
- * call returns.
- */
- public FilePart(@NonNull String partKey, @NonNull PersistableBundle partParams,
- @NonNull FileInputStream fileInputStream)
- throws IOException {
- this(partKey, partParams, ParcelFileDescriptor.dup(fileInputStream.getFD()));
- }
-
- /**
- * Returns a FileInputStream for the associated File.
- * Caller must close the associated stream when done reading from it.
- *
- * @return the FileInputStream associated with the FilePart.
- */
- @NonNull
- public FileInputStream getFileInputStream() {
- return new FileInputStream(mParcelFileDescriptor.getFileDescriptor());
- }
-
- /**
- * Returns the unique key associated with the part. Each Part key added to a content object
- * should be ensured to be unique.
- */
- @NonNull
- public String getFilePartKey() {
- return mPartKey;
- }
-
- /**
- * Returns the params associated with Part.
- */
- @NonNull
- public PersistableBundle getFilePartParams() {
- return mPartParams;
- }
-
-
- @Override
- public int describeContents() {
- return CONTENTS_FILE_DESCRIPTOR;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeString8(getFilePartKey());
- dest.writePersistableBundle(getFilePartParams());
- mParcelFileDescriptor.writeToParcel(dest, flags
- | Parcelable.PARCELABLE_WRITE_RETURN_VALUE); // This flag ensures that the sender's
- // copy of the Pfd is closed as soon as the Binder call succeeds.
- }
-
- @NonNull
- public static final Creator<FilePart> CREATOR = new Creator<>() {
- @Override
- public FilePart createFromParcel(Parcel in) {
- return new FilePart(in.readString(), in.readTypedObject(PersistableBundle.CREATOR),
- in.readParcelable(
- getClass().getClassLoader(), ParcelFileDescriptor.class));
- }
-
- @Override
- public FilePart[] newArray(int size) {
- return new FilePart[size];
- }
- };
-}
diff --git a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
index b925f48..360a809 100644
--- a/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
+++ b/core/java/android/app/ondeviceintelligence/IOnDeviceIntelligenceManager.aidl
@@ -31,7 +31,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
- import android.app.ondeviceintelligence.ITokenCountCallback;
+ import android.app.ondeviceintelligence.ITokenInfoCallback;
/**
@@ -56,8 +56,8 @@
void requestFeatureDownload(in Feature feature, ICancellationSignal signal, in IDownloadCallback callback) = 5;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal signal,
- in ITokenCountCallback tokenCountcallback) = 6;
+ void requestTokenInfo(in Feature feature, in Content request, in ICancellationSignal signal,
+ in ITokenInfoCallback tokenInfocallback) = 6;
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)")
void processRequest(in Feature feature, in Content request, int requestType, in ICancellationSignal cancellationSignal, in IProcessingSignal signal,
diff --git a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
index 9848e1d..0adf305 100644
--- a/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IResponseCallback.aidl
@@ -3,6 +3,7 @@
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
* Interface for a IResponseCallback for receiving response from on-device intelligence service.
@@ -12,4 +13,5 @@
interface IResponseCallback {
void onSuccess(in Content result) = 1;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 3;
}
diff --git a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
index a680574..132e53e 100644
--- a/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
+++ b/core/java/android/app/ondeviceintelligence/IStreamingResponseCallback.aidl
@@ -4,6 +4,7 @@
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.os.PersistableBundle;
+import android.os.RemoteCallback;
/**
@@ -15,4 +16,5 @@
void onNewContent(in Content result) = 1;
void onSuccess(in Content result) = 2;
void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 3;
+ void onDataAugmentRequest(in Content content, in RemoteCallback contentCallback) = 4;
}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
deleted file mode 100644
index b724e03..0000000
--- a/core/java/android/app/ondeviceintelligence/ITokenCountCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.app.ondeviceintelligence;
-
-import android.os.PersistableBundle;
-
-/**
- * Interface for receiving the token count of a request for a given features.
- *
- * @hide
- */
-interface ITokenCountCallback {
- void onSuccess(long tokenCount) = 1;
- void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
-}
diff --git a/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
new file mode 100644
index 0000000..9219a89
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ITokenInfoCallback.aidl
@@ -0,0 +1,14 @@
+package android.app.ondeviceintelligence;
+
+import android.os.PersistableBundle;
+import android.app.ondeviceintelligence.TokenInfo;
+
+/**
+ * Interface for receiving the token info of a request for a given feature.
+ *
+ * @hide
+ */
+interface ITokenInfoCallback {
+ void onSuccess(in TokenInfo tokenInfo) = 1;
+ void onFailure(int errorCode, in String errorMessage, in PersistableBundle errorParams) = 2;
+}
diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
index 4d8e0d5..d195c4d 100644
--- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
+++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@@ -26,8 +26,10 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -37,6 +39,8 @@
import androidx.annotation.IntDef;
+import com.android.internal.R;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -60,7 +64,16 @@
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
public class OnDeviceIntelligenceManager {
+ /**
+ * @hide
+ */
public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey";
+
+ /**
+ * @hide
+ */
+ public static final String AUGMENT_REQUEST_CONTENT_BUNDLE_KEY =
+ "AugmentRequestContentBundleKey";
private final Context mContext;
private final IOnDeviceIntelligenceManager mService;
@@ -82,8 +95,6 @@
public void getVersion(
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull LongConsumer versionConsumer) {
- // TODO explore modifying this method into getServicePackageDetails and return both
- // version and package name of the remote service implementing this.
try {
RemoteCallback callback = new RemoteCallback(result -> {
if (result == null) {
@@ -100,6 +111,23 @@
}
}
+
+ /**
+ * Get package name configured for providing the remote implementation for this system service.
+ */
+ @Nullable
+ @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
+ public String getRemoteServicePackageName() {
+ String serviceConfigValue = mContext.getResources().getString(
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
+ ComponentName componentName = ComponentName.unflattenFromString(serviceConfigValue);
+ if (componentName != null) {
+ return componentName.getPackageName();
+ }
+
+ return null;
+ }
+
/**
* Asynchronously get feature for a given id.
*
@@ -273,29 +301,29 @@
}
/**
- * The methods computes the token-count for a given request payload using the provided Feature
- * details.
+ * The methods computes the token related information for a given request payload using the
+ * provided {@link Feature}.
*
* @param feature feature associated with the request.
* @param request request that contains the content data and associated params.
- * @param outcomeReceiver callback to populate the token count or exception in case of
+ * @param outcomeReceiver callback to populate the token info or exception in case of
* failure.
* @param cancellationSignal signal to invoke cancellation on the operation in the remote
* implementation.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void requestTokenCount(@NonNull Feature feature, @NonNull Content request,
+ public void requestTokenInfo(@NonNull Feature feature, @NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManagerException> outcomeReceiver) {
try {
- ITokenCountCallback callback = new ITokenCountCallback.Stub() {
+ ITokenInfoCallback callback = new ITokenInfoCallback.Stub() {
@Override
- public void onSuccess(long tokenCount) {
+ public void onSuccess(TokenInfo tokenInfo) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> outcomeReceiver.onResult(tokenCount)));
+ () -> outcomeReceiver.onResult(tokenInfo)));
}
@Override
@@ -314,7 +342,7 @@
cancellationSignal.setRemote(transport);
}
- mService.requestTokenCount(feature, request, transport, callback);
+ mService.requestTokenInfo(feature, request, transport, callback);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -328,33 +356,31 @@
* was a
* failure.
*
- * @param feature feature associated with the request.
- * @param request request that contains the Content data and
- * associated params.
- * @param requestType type of request being sent for processing the content.
- * @param responseOutcomeReceiver callback to populate the response content and
- * associated
- * params.
- * @param processingSignal signal to invoke custom actions in the
- * remote implementation.
- * @param cancellationSignal signal to invoke cancellation or
- * @param callbackExecutor executor to run the callback on.
+ * @param feature feature associated with the request.
+ * @param request request that contains the Content data and
+ * associated params.
+ * @param requestType type of request being sent for processing the content.
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
+ * remote implementation.
+ * @param callbackExecutor executor to run the callback on.
+ * @param responseCallback callback to populate the response content and
+ * associated params.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequest(@NonNull Feature feature, @NonNull Content request,
+ public void processRequest(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManagerProcessingException> responseOutcomeReceiver) {
+ @NonNull ProcessingOutcomeReceiver responseCallback) {
try {
IResponseCallback callback = new IResponseCallback.Stub() {
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> responseOutcomeReceiver.onResult(result));
+ callbackExecutor.execute(() -> responseCallback.onResult(result));
});
}
@@ -362,12 +388,24 @@
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
- () -> responseOutcomeReceiver.onError(
+ () -> responseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams))));
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> responseCallback.onDataAugmentRequest(content, result -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
+ callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
+ })));
+ }
};
+
IProcessingSignal transport = null;
if (processingSignal != null) {
transport = ProcessingSignal.createTransport();
@@ -389,46 +427,48 @@
}
/**
- * Variation of {@link #processRequest} that asynchronously processes a request in a streaming
+ * Variation of {@link #processRequest} that asynchronously processes a request in a
+ * streaming
* fashion, where new content is pushed to caller in chunks via the
- * {@link StreamingResponseReceiver#onNewContent}. After the streaming is complete,
- * the service should call {@link StreamingResponseReceiver#onResult} and can optionally
- * populate the complete {@link Response}'s Content as part of the callback when the final
- * {@link Response} contains an enhanced aggregation of the Contents already streamed.
+ * {@link StreamedProcessingOutcomeReceiver#onNewContent}. After the streaming is complete,
+ * the service should call {@link StreamedProcessingOutcomeReceiver#onResult} and can optionally
+ * populate the complete the full response {@link Content} as part of the callback in cases
+ * when the final response contains an enhanced aggregation of the Contents already
+ * streamed.
*
* @param feature feature associated with the request.
* @param request request that contains the Content data and associated
* params.
* @param requestType type of request being sent for processing the content.
- * @param processingSignal signal to invoke other custom actions in the
+ * @param cancellationSignal signal to invoke cancellation.
+ * @param processingSignal signal to send custom signals in the
* remote implementation.
- * @param cancellationSignal signal to invoke cancellation
- * @param streamingResponseReceiver streaming callback to populate the response content and
+ * @param streamingResponseCallback streaming callback to populate the response content and
* associated params.
* @param callbackExecutor executor to run the callback on.
*/
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
- public void processRequestStreaming(@NonNull Feature feature, @NonNull Content request,
+ public void processRequestStreaming(@NonNull Feature feature, @Nullable Content request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManagerProcessingException> streamingResponseReceiver) {
+ @NonNull StreamedProcessingOutcomeReceiver streamingResponseCallback) {
try {
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(Content result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onNewContent(result));
+ () -> streamingResponseCallback.onNewContent(result));
});
}
@Override
public void onSuccess(Content result) {
Binder.withCleanCallingIdentity(() -> {
- callbackExecutor.execute(() -> streamingResponseReceiver.onResult(result));
+ callbackExecutor.execute(
+ () -> streamingResponseCallback.onResult(result));
});
}
@@ -437,11 +477,26 @@
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
- () -> streamingResponseReceiver.onError(
+ () -> streamingResponseCallback.onError(
new OnDeviceIntelligenceManagerProcessingException(
errorCode, errorMessage, errorParams)));
});
}
+
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull RemoteCallback contentCallback) {
+ Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
+ () -> streamingResponseCallback.onDataAugmentRequest(content,
+ contentResponse -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ contentResponse);
+ callbackExecutor.execute(
+ () -> contentCallback.sendResult(bundle));
+ })));
+ }
};
IProcessingSignal transport = null;
@@ -468,7 +523,8 @@
public static final int REQUEST_TYPE_INFERENCE = 0;
/**
- * Prepares the remote implementation environment for e.g.loading inference runtime etc.which
+ * Prepares the remote implementation environment for e.g.loading inference runtime etc
+ * .which
* are time consuming beforehand to remove overhead and allow quick processing of requests
* thereof.
*/
@@ -485,7 +541,8 @@
REQUEST_TYPE_PREPARE,
REQUEST_TYPE_EMBEDDINGS
}, open = true)
- @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+ @Target({ElementType.TYPE_USE, ElementType.METHOD, ElementType.PARAMETER,
+ ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface RequestType {
}
@@ -501,17 +558,32 @@
*/
public static final int ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE = 1000;
+ /**
+ * Error code to be used for on device intelligence manager failures.
+ *
+ * @hide
+ */
+ @IntDef(
+ value = {
+ ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE
+ }, open = true)
+ @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+ @interface OnDeviceIntelligenceManagerErrorCode {
+ }
+
private final int mErrorCode;
private final PersistableBundle errorParams;
- public OnDeviceIntelligenceManagerException(int errorCode, @NonNull String errorMessage,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode, @NonNull String errorMessage,
@NonNull PersistableBundle errorParams) {
super(errorMessage);
this.mErrorCode = errorCode;
this.errorParams = errorParams;
}
- public OnDeviceIntelligenceManagerException(int errorCode,
+ public OnDeviceIntelligenceManagerException(
+ @OnDeviceIntelligenceManagerErrorCode int errorCode,
@NonNull PersistableBundle errorParams) {
this.mErrorCode = errorCode;
this.errorParams = errorParams;
@@ -573,11 +645,15 @@
/** Inference suspended so that higher-priority inference can run. */
public static final int PROCESSING_ERROR_SUSPENDED = 13;
- /** Underlying processing encountered an internal error, like a violated precondition. */
+ /**
+ * Underlying processing encountered an internal error, like a violated precondition
+ * .
+ */
public static final int PROCESSING_ERROR_INTERNAL = 14;
/**
- * The processing was not able to be passed on to the remote implementation, as the service
+ * The processing was not able to be passed on to the remote implementation, as the
+ * service
* was unavailable.
*/
public static final int PROCESSING_ERROR_SERVICE_UNAVAILABLE = 15;
diff --git a/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
new file mode 100644
index 0000000..b0b6e19
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/ProcessingOutcomeReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.OutcomeReceiver;
+
+import java.util.function.Consumer;
+
+/**
+ * Response Callback to populate the processed response or any error that occurred during the
+ * request processing. This callback also provides a method to request additional data to be
+ * augmented to the request-processing, using the partial {@link Content} that was already
+ * processed in the remote implementation.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public interface ProcessingOutcomeReceiver extends
+ OutcomeReceiver<Content,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> {
+ /**
+ * Callback to be invoked in cases where the remote service needs to perform retrieval or
+ * transformation operations based on a partially processed request, in order to augment the
+ * final response, by using the additional context sent via this callback.
+ *
+ * @param content The content payload that should be used to augment ongoing request.
+ * @param contentConsumer The augmentation data that should be sent to remote
+ * service for further processing a request.
+ */
+ default void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentConsumer) {
+ contentConsumer.accept(null);
+ }
+}
diff --git a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
similarity index 71%
rename from core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
rename to core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
index ebcf61c..ac2b032 100644
--- a/core/java/android/app/ondeviceintelligence/StreamingResponseReceiver.java
+++ b/core/java/android/app/ondeviceintelligence/StreamedProcessingOutcomeReceiver.java
@@ -21,23 +21,19 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.os.OutcomeReceiver;
/**
* Streaming variant of outcome receiver to populate response while processing a given request,
- * possibly in
- * chunks to provide a async processing behaviour to the caller.
+ * possibly in chunks to provide a async processing behaviour to the caller.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public interface StreamingResponseReceiver<R, T, E extends Throwable> extends
- OutcomeReceiver<R, E> {
+public interface StreamedProcessingOutcomeReceiver extends ProcessingOutcomeReceiver {
/**
- * Callback to be invoked when a part of the response i.e. some {@link Content} is already
- * processed and
- * needs to be passed onto the caller.
+ * Callback that would be invoked when a part of the response i.e. some {@link Content} is
+ * already processed and needs to be passed onto the caller.
*/
- void onNewContent(@NonNull T content);
+ void onNewContent(@NonNull Content content);
}
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
similarity index 74%
rename from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
rename to core/java/android/app/ondeviceintelligence/TokenInfo.aidl
index 838e41e..2c19c1e 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.aidl
@@ -1,5 +1,5 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package android.hardware;
+package android.app.ondeviceintelligence;
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
-
+/**
+ * @hide
+ */
+parcelable TokenInfo;
diff --git a/core/java/android/app/ondeviceintelligence/TokenInfo.java b/core/java/android/app/ondeviceintelligence/TokenInfo.java
new file mode 100644
index 0000000..035cc4b
--- /dev/null
+++ b/core/java/android/app/ondeviceintelligence/TokenInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.ondeviceintelligence;
+
+import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+
+/**
+ * This class is used to provide a token count response for the
+ * {@link OnDeviceIntelligenceManager#requestTokenInfo} outcome receiver.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
+public final class TokenInfo implements Parcelable {
+ private final long mCount;
+ private final PersistableBundle mInfoParams;
+
+ /**
+ * Construct a token count using the count value and associated params.
+ */
+ public TokenInfo(long count, @NonNull PersistableBundle persistableBundle) {
+ this.mCount = count;
+ mInfoParams = persistableBundle;
+ }
+
+ /**
+ * Construct a token count using the count value.
+ */
+ public TokenInfo(long count) {
+ this.mCount = count;
+ this.mInfoParams = new PersistableBundle();
+ }
+
+ /**
+ * Returns the token count associated with a request payload.
+ */
+ public long getCount() {
+ return mCount;
+ }
+
+ /**
+ * Returns the params representing token info.
+ */
+ @NonNull
+ public PersistableBundle getInfoParams() {
+ return mInfoParams;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mCount);
+ dest.writePersistableBundle(mInfoParams);
+ }
+
+ public static final @NonNull Parcelable.Creator<TokenInfo> CREATOR
+ = new Parcelable.Creator<>() {
+ @Override
+ public TokenInfo[] newArray(int size) {
+ return new TokenInfo[size];
+ }
+
+ @Override
+ public TokenInfo createFromParcel(@NonNull Parcel in) {
+ return new TokenInfo(in.readLong(), in.readPersistableBundle());
+ }
+ };
+}
diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
index 1a8136e..7383d07 100644
--- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java
+++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java
@@ -16,16 +16,24 @@
package android.app.servertransaction;
+import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.bundleClientTransactionFlag;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
+import android.app.Activity;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.window.ActivityWindowInfo;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.function.BiConsumer;
+
/**
* Singleton controller to manage listeners to individual {@link ClientTransaction}.
*
@@ -35,8 +43,14 @@
private static ClientTransactionListenerController sController;
+ private final Object mLock = new Object();
private final DisplayManagerGlobal mDisplayManager;
+ /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
+ @GuardedBy("mLock")
+ private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
+ mActivityWindowInfoChangedListeners = new ArraySet<>();
+
/** Gets the singleton controller. */
@NonNull
public static ClientTransactionListenerController getInstance() {
@@ -62,6 +76,57 @@
}
/**
+ * Registers to listen on activity {@link ActivityWindowInfo} change.
+ * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
+ * {@link ActivityWindowInfo}.
+ */
+ public void registerActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Unregisters the listener that was previously registered via
+ * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
+ */
+ public void unregisterActivityWindowInfoChangedListener(
+ @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ mActivityWindowInfoChangedListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Called when receives a {@link ClientTransaction} that is updating an activity's
+ * {@link ActivityWindowInfo}.
+ */
+ public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ if (!activityWindowInfoFlag()) {
+ return;
+ }
+ final Object[] activityWindowInfoChangedListeners;
+ synchronized (mLock) {
+ if (mActivityWindowInfoChangedListeners.isEmpty()) {
+ return;
+ }
+ activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
+ }
+ for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
+ ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
+ .accept(activityToken, activityWindowInfo);
+ }
+ }
+
+ /**
* Called when receives a {@link ClientTransaction} that is updating display-related
* window configuration.
*/
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index f9cf075..b0213d7 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,7 +33,6 @@
public class DestroyActivityItem extends ActivityLifecycleItem {
private boolean mFinished;
- private int mConfigChanges;
@Override
public void preExecute(@NonNull ClientTransactionHandler client) {
@@ -44,7 +43,7 @@
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- client.handleDestroyActivity(r, mFinished, mConfigChanges,
+ client.handleDestroyActivity(r, mFinished,
false /* getNonConfigInstance */, "DestroyActivityItem");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -67,15 +66,13 @@
/** Obtain an instance initialized with provided params. */
@NonNull
- public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
- int configChanges) {
+ public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished) {
DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
if (instance == null) {
instance = new DestroyActivityItem();
}
instance.setActivityToken(activityToken);
instance.mFinished = finished;
- instance.mConfigChanges = configChanges;
return instance;
}
@@ -84,7 +81,6 @@
public void recycle() {
super.recycle();
mFinished = false;
- mConfigChanges = 0;
ObjectPool.recycle(this);
}
@@ -95,14 +91,12 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
- dest.writeInt(mConfigChanges);
}
/** Read from Parcel. */
private DestroyActivityItem(@NonNull Parcel in) {
super(in);
mFinished = in.readBoolean();
- mConfigChanges = in.readInt();
}
public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() {
@@ -124,7 +118,7 @@
return false;
}
final DestroyActivityItem other = (DestroyActivityItem) o;
- return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+ return mFinished == other.mFinished;
}
@Override
@@ -132,14 +126,12 @@
int result = 17;
result = 31 * result + super.hashCode();
result = 31 * result + (mFinished ? 1 : 0);
- result = 31 * result + mConfigChanges;
return result;
}
@Override
public String toString() {
return "DestroyActivityItem{" + super.toString()
- + ",finished=" + mFinished
- + ",mConfigChanges=" + mConfigChanges + "}";
+ + ",finished=" + mFinished + "}";
}
}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 8f1e90b..d230284 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -37,7 +37,6 @@
private boolean mFinished;
private boolean mUserLeaving;
- private int mConfigChanges;
private boolean mDontReport;
private boolean mAutoEnteringPip;
@@ -45,7 +44,7 @@
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
+ client.handlePauseActivity(r, mFinished, mUserLeaving, mAutoEnteringPip,
pendingActions, "PAUSE_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -72,7 +71,7 @@
/** Obtain an instance initialized with provided params. */
@NonNull
public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) {
+ boolean userLeaving, boolean dontReport, boolean autoEnteringPip) {
PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
if (instance == null) {
instance = new PauseActivityItem();
@@ -80,7 +79,6 @@
instance.setActivityToken(activityToken);
instance.mFinished = finished;
instance.mUserLeaving = userLeaving;
- instance.mConfigChanges = configChanges;
instance.mDontReport = dontReport;
instance.mAutoEnteringPip = autoEnteringPip;
@@ -91,7 +89,7 @@
@NonNull
public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
return obtain(activityToken, false /* finished */, false /* userLeaving */,
- 0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/);
+ true /* dontReport */, false /* autoEnteringPip*/);
}
@Override
@@ -99,7 +97,6 @@
super.recycle();
mFinished = false;
mUserLeaving = false;
- mConfigChanges = 0;
mDontReport = false;
mAutoEnteringPip = false;
ObjectPool.recycle(this);
@@ -113,7 +110,6 @@
super.writeToParcel(dest, flags);
dest.writeBoolean(mFinished);
dest.writeBoolean(mUserLeaving);
- dest.writeInt(mConfigChanges);
dest.writeBoolean(mDontReport);
dest.writeBoolean(mAutoEnteringPip);
}
@@ -123,7 +119,6 @@
super(in);
mFinished = in.readBoolean();
mUserLeaving = in.readBoolean();
- mConfigChanges = in.readInt();
mDontReport = in.readBoolean();
mAutoEnteringPip = in.readBoolean();
}
@@ -148,7 +143,7 @@
}
final PauseActivityItem other = (PauseActivityItem) o;
return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
- && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport
+ && mDontReport == other.mDontReport
&& mAutoEnteringPip == other.mAutoEnteringPip;
}
@@ -158,7 +153,6 @@
result = 31 * result + super.hashCode();
result = 31 * result + (mFinished ? 1 : 0);
result = 31 * result + (mUserLeaving ? 1 : 0);
- result = 31 * result + mConfigChanges;
result = 31 * result + (mDontReport ? 1 : 0);
result = 31 * result + (mAutoEnteringPip ? 1 : 0);
return result;
@@ -169,7 +163,6 @@
return "PauseActivityItem{" + super.toString()
+ ",finished=" + mFinished
+ ",userLeaving=" + mUserLeaving
- + ",configChanges=" + mConfigChanges
+ ",dontReport=" + mDontReport
+ ",autoEnteringPip=" + mAutoEnteringPip + "}";
}
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index b8ce52d..def7b3f 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -19,7 +19,6 @@
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.ClientTransactionHandler;
import android.os.IBinder;
@@ -34,13 +33,11 @@
private static final String TAG = "StopActivityItem";
- private int mConfigChanges;
-
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
@NonNull PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- client.handleStopActivity(r, mConfigChanges, pendingActions,
+ client.handleStopActivity(r, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -63,16 +60,14 @@
/**
* Obtain an instance initialized with provided params.
* @param activityToken the activity that stops.
- * @param configChanges Configuration pieces that changed.
*/
@NonNull
- public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) {
+ public static StopActivityItem obtain(@NonNull IBinder activityToken) {
StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
if (instance == null) {
instance = new StopActivityItem();
}
instance.setActivityToken(activityToken);
- instance.mConfigChanges = configChanges;
return instance;
}
@@ -80,23 +75,14 @@
@Override
public void recycle() {
super.recycle();
- mConfigChanges = 0;
ObjectPool.recycle(this);
}
// Parcelable implementation
- /** Write to Parcel. */
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- super.writeToParcel(dest, flags);
- dest.writeInt(mConfigChanges);
- }
-
/** Read from Parcel. */
private StopActivityItem(@NonNull Parcel in) {
super(in);
- mConfigChanges = in.readInt();
}
public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() {
@@ -110,28 +96,7 @@
};
@Override
- public boolean equals(@Nullable Object o) {
- if (this == o) {
- return true;
- }
- if (!super.equals(o)) {
- return false;
- }
- final StopActivityItem other = (StopActivityItem) o;
- return mConfigChanges == other.mConfigChanges;
- }
-
- @Override
- public int hashCode() {
- int result = 17;
- result = 31 * result + super.hashCode();
- result = 31 * result + mConfigChanges;
- return result;
- }
-
- @Override
public String toString() {
- return "StopActivityItem{" + super.toString()
- + ",configChanges=" + mConfigChanges + "}";
+ return "StopActivityItem{" + super.toString() + "}";
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index fa73c99..c837191 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -334,18 +334,18 @@
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */,
+ false /* userLeaving */,
false /* autoEnteringPip */, mPendingActions,
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
- mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
+ mTransactionHandler.handleStopActivity(r,
mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
- 0 /* configChanges */, false /* getNonConfigInstance */,
+ false /* getNonConfigInstance */,
"performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 475c6fb..710261a 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -200,7 +200,7 @@
lifecycleItem = PauseActivityItem.obtain(r.token);
break;
case ON_STOP:
- lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */);
+ lifecycleItem = StopActivityItem.obtain(r.token);
break;
default:
lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index eb82e1f..cda4d89 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1417,13 +1417,15 @@
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD
* @see AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX
+ *
+ * @return true if the call was successful, false if it was rate-limited.
*/
@FlaggedApi(Flags.FLAG_GENERATED_PREVIEWS)
- public void setWidgetPreview(@NonNull ComponentName provider,
+ public boolean setWidgetPreview(@NonNull ComponentName provider,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
try {
- mService.setWidgetPreview(provider, widgetCategories, preview);
+ return mService.setWidgetPreview(provider, widgetCategories, preview);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 70d2c7a..7f2ec53 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5067,6 +5067,7 @@
* {@link android.hardware.fingerprint.FingerprintManager} for handling management
* of fingerprints.
*
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt}
* @see #getSystemService(String)
* @see android.hardware.fingerprint.FingerprintManager
*/
@@ -5081,8 +5082,6 @@
* @see #getSystemService
* @see android.hardware.face.FaceManager
*/
- @FlaggedApi(android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
public static final String FACE_SERVICE = "face";
/**
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 42dd87a..443aadd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -8183,7 +8183,7 @@
// launch flags
else if (uri.startsWith("launchFlags=", i)) {
- intent.mFlags = Integer.decode(value);
+ intent.mFlags = decodeInteger(value);
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -8191,7 +8191,7 @@
// extended flags
else if (uri.startsWith("extendedLaunchFlags=", i)) {
- intent.mExtendedFlags = Integer.decode(value);
+ intent.mExtendedFlags = decodeInteger(value);
}
// package
@@ -8401,7 +8401,7 @@
isIntentFragment = true;
i += 12;
int j = uri.indexOf(')', i);
- intent.mFlags = Integer.decode(uri.substring(i, j));
+ intent.mFlags = decodeInteger(uri.substring(i, j));
if ((flags& URI_ALLOW_UNSAFE) == 0) {
intent.mFlags &= ~IMMUTABLE_FLAGS;
}
@@ -8512,6 +8512,23 @@
return intent;
}
+ private static Integer decodeInteger(String value) {
+ try {
+ return Integer.decode(value);
+ } catch (NumberFormatException e) {
+ try {
+ if (value != null && value.startsWith("0x")) {
+ // In toUriInner, we do "0x".append(Integer.toHexString).
+ // Sometimes "decode" fails to parse, e.g. 0x90000000.
+ return Integer.parseUnsignedInt(value.substring(2), 16);
+ }
+ } catch (NumberFormatException ignored) {
+ // ignored, throw the original exception
+ }
+ throw e;
+ }
+ }
+
/** @hide */
public interface CommandOptionHandler {
boolean handleOption(String opt, ShellCommand cmd);
@@ -8577,7 +8594,7 @@
case "--ei": {
String key = cmd.getNextArgRequired();
String value = cmd.getNextArgRequired();
- intent.putExtra(key, Integer.decode(value));
+ intent.putExtra(key, decodeInteger(value));
}
break;
case "--eu": {
@@ -8601,7 +8618,7 @@
String[] strings = value.split(",");
int[] list = new int[strings.length];
for (int i = 0; i < strings.length; i++) {
- list[i] = Integer.decode(strings[i]);
+ list[i] = decodeInteger(strings[i]);
}
intent.putExtra(key, list);
}
@@ -8612,7 +8629,7 @@
String[] strings = value.split(",");
ArrayList<Integer> list = new ArrayList<>(strings.length);
for (int i = 0; i < strings.length; i++) {
- list.add(Integer.decode(strings[i]));
+ list.add(decodeInteger(strings[i]));
}
intent.putExtra(key, list);
}
@@ -8747,7 +8764,7 @@
arg = false;
} else {
try {
- arg = Integer.decode(value) != 0;
+ arg = decodeInteger(value) != 0;
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid boolean value: " + value);
}
@@ -8777,7 +8794,7 @@
break;
case "-f":
String str = cmd.getNextArgRequired();
- intent.setFlags(Integer.decode(str).intValue());
+ intent.setFlags(decodeInteger(str).intValue());
break;
case "--grant-read-uri-permission":
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 6bb9c33..41c1f17 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -697,8 +697,9 @@
public List<UserHandle> getProfiles() {
if (mUserManager.isManagedProfile()
|| (android.multiuser.Flags.enableLauncherAppsHiddenProfileChecks()
- && android.os.Flags.allowPrivateProfile()
- && mUserManager.isPrivateProfile())) {
+ && android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && mUserManager.isPrivateProfile())) {
// If it's a managed or private profile, only return the current profile.
final List result = new ArrayList(1);
result.add(android.os.Process.myUserHandle());
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index cdda12e..3f941da 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -273,6 +273,10 @@
* to the <code>retailDemo</code> value of
* {@link android.R.attr#protectionLevel}.
*
+ * @deprecated This flag has been replaced by the
+ * {@link android.R.string#config_defaultRetailDemo retail demo role} and is a
+ * no-op since {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}.
+ *
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 5e9d8f0..610057b 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -208,6 +208,14 @@
}
flag {
+ name: "restrict_nonpreloads_system_shareduids"
+ namespace: "package_manager_service"
+ description: "Feature flag to restrict apps from joining system shared uids"
+ bug: "308573169"
+ is_fixed_read_only: true
+}
+
+flag {
name: "min_target_sdk_24"
namespace: "responsible_apis"
description: "Feature flag to bump min target sdk to 24"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index ac80561..48a7cc9 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -184,3 +184,11 @@
description: "Enable Private Space telephony and SMS intent redirection to the main user"
bug: "325576602"
}
+
+flag {
+ name: "block_private_space_creation"
+ namespace: "profile_experiences"
+ description: "Allow blocking private space creation based on specific conditions"
+ bug: "290333800"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
index 4dcc517..a908456 100644
--- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
+++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java
@@ -163,14 +163,31 @@
}
/**
- * Update the URI relative filter groups for a package. All previously existing groups
- * will be cleared before the new groups will be applied.
+ * Update the URI relative filter groups for a package. The groups set using this API acts
+ * as an additional filtering layer during intent resolution. It does not replace any
+ * existing groups that have been added to the package's intent filters either using the
+ * {@link android.content.IntentFilter#addUriRelativeFilterGroup(UriRelativeFilterGroup)}
+ * API or defined in the manifest.
+ * <p>
+ * Groups can be indexed to any domain or can be indexed for all subdomains by prefixing the
+ * hostname with a wildcard (i.e. "*.example.com"). Priority will be first given to groups
+ * that are indexed to the specific subdomain of the intent's data URI followed by any groups
+ * indexed to wildcard subdomains. If the subdomain consists of more than one label, priority
+ * will decrease corresponding to the decreasing number of subdomain labels after the wildcard.
+ * For example "a.b.c.d" will match "*.b.c.d" before "*.c.d".
+ * <p>
+ * All previously existing groups set for a domain index using this API will be cleared when
+ * new groups are set.
*
* @param packageName The name of the package.
* @param domainToGroupsMap A map of domains to a list of {@link UriRelativeFilterGroup}s that
* should apply to them. Groups for each domain will replace any groups
- * provided for that domain in a prior call to this method. Groups will
+ * provided for that domain in a prior call to this method. To clear
+ * existing groups, set the list to null or a empty list. Groups will
* be evaluated in the order they are provided.
+ *
+ * @see UriRelativeFilterGroup
+ * @see android.content.IntentFilter
* @hide
*/
@SystemApi
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index d259e97..273e40a 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -471,6 +471,16 @@
return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
}
+ /**
+ * @hide
+ */
+ public void addSharedLibraryPaths(@NonNull String[] paths) {
+ final int length = paths.length;
+ for (int i = 0; i < length; i++) {
+ addAssetPathInternal(paths[i], false, true);
+ }
+ }
+
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Objects.requireNonNull(path, "path");
synchronized (this) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 7fba3e8..1f5f88f 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -43,6 +43,7 @@
import android.annotation.StyleableRes;
import android.annotation.XmlRes;
import android.app.Application;
+import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -2854,6 +2855,11 @@
@FlaggedApi(android.content.res.Flags.FLAG_REGISTER_RESOURCE_PATHS)
public static void registerResourcePaths(@NonNull String uniqueId,
@NonNull ApplicationInfo appInfo) {
- throw new UnsupportedOperationException("The implementation has not been done yet.");
+ if (Flags.registerResourcePaths()) {
+ ResourcesManager.getInstance().registerResourcePaths(uniqueId, appInfo);
+ } else {
+ throw new UnsupportedOperationException("Flag " + Flags.FLAG_REGISTER_RESOURCE_PATHS
+ + " is disabled.");
+ }
}
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 079c2c1..8d045aa 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,6 +29,7 @@
import android.annotation.StyleableRes;
import android.app.LocaleConfig;
import android.app.ResourcesManager;
+import android.app.ResourcesManager.SharedLibraryAssets;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
@@ -47,6 +48,7 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -197,6 +199,14 @@
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
+ if (Flags.registerResourcePaths()) {
+ ArrayMap<String, SharedLibraryAssets> sharedLibMap =
+ ResourcesManager.getInstance().getSharedLibAssetsMap();
+ final int size = sharedLibMap.size();
+ for (int i = 0; i < size; i++) {
+ assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths());
+ }
+ }
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java
index 4b0fa6d..79fba9b 100644
--- a/core/java/android/credentials/selection/IntentFactory.java
+++ b/core/java/android/credentials/selection/IntentFactory.java
@@ -80,17 +80,7 @@
ArrayList<DisabledProviderData> disabledProviderDataList,
@NonNull ResultReceiver resultReceiver) {
Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerDialogComponent));
- ComponentName oemOverrideComponentName = getOemOverrideComponentName(context);
- if (oemOverrideComponentName != null) {
- componentName = oemOverrideComponentName;
- }
- intent.setComponent(componentName);
+ setCredentialSelectorUiComponentName(context, intent);
intent.putParcelableArrayListExtra(
ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
@@ -100,6 +90,24 @@
return intent;
}
+ private static void setCredentialSelectorUiComponentName(@NonNull Context context,
+ @NonNull Intent intent) {
+ if (configurableSelectorUiEnabled()) {
+ ComponentName componentName = getOemOverrideComponentName(context);
+ if (componentName == null) {
+ componentName = ComponentName.unflattenFromString(Resources.getSystem().getString(
+ com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ }
+ intent.setComponent(componentName);
+ } else {
+ ComponentName componentName = ComponentName.unflattenFromString(Resources.getSystem()
+ .getString(com.android.internal.R.string
+ .config_fallbackCredentialManagerDialogComponent));
+ intent.setComponent(componentName);
+ }
+ }
+
/**
* Returns null if there is not an enabled and valid oem override component. It means the
* default platform UI component name should be used instead.
@@ -107,44 +115,39 @@
@Nullable
private static ComponentName getOemOverrideComponentName(@NonNull Context context) {
ComponentName result = null;
- if (configurableSelectorUiEnabled()) {
- if (Resources.getSystem().getBoolean(
- com.android.internal.R.bool.config_enableOemCredentialManagerDialogComponent)) {
- String oemComponentString =
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_oemCredentialManagerDialogComponent);
- if (!TextUtils.isEmpty(oemComponentString)) {
- ComponentName oemComponentName = ComponentName.unflattenFromString(
- oemComponentString);
- if (oemComponentName != null) {
- try {
- ActivityInfo info = context.getPackageManager().getActivityInfo(
- oemComponentName,
- PackageManager.ComponentInfoFlags.of(
- PackageManager.MATCH_SYSTEM_ONLY));
- if (info.enabled && info.exported) {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component."
- + oemComponentString);
- result = oemComponentName;
- } else {
- Slog.i(TAG,
- "Found enabled oem CredMan UI component but it was not "
- + "enabled.");
- }
- } catch (PackageManager.NameNotFoundException e) {
- Slog.i(TAG, "Unable to find oem CredMan UI component: "
- + oemComponentString + ".");
- }
+ String oemComponentString =
+ Resources.getSystem()
+ .getString(
+ com.android.internal.R.string
+ .config_oemCredentialManagerDialogComponent);
+ if (!TextUtils.isEmpty(oemComponentString)) {
+ ComponentName oemComponentName = ComponentName.unflattenFromString(
+ oemComponentString);
+ if (oemComponentName != null) {
+ try {
+ ActivityInfo info = context.getPackageManager().getActivityInfo(
+ oemComponentName,
+ PackageManager.ComponentInfoFlags.of(
+ PackageManager.MATCH_SYSTEM_ONLY));
+ if (info.enabled && info.exported) {
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component."
+ + oemComponentString);
+ result = oemComponentName;
} else {
- Slog.i(TAG, "Invalid OEM ComponentName format.");
+ Slog.i(TAG,
+ "Found enabled oem CredMan UI component but it was not "
+ + "enabled.");
}
- } else {
- Slog.i(TAG, "Invalid empty OEM component name.");
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.i(TAG, "Unable to find oem CredMan UI component: "
+ + oemComponentString + ".");
}
+ } else {
+ Slog.i(TAG, "Invalid OEM ComponentName format.");
}
+ } else {
+ Slog.i(TAG, "Invalid empty OEM component name.");
}
return result;
}
@@ -186,16 +189,11 @@
* Creates an Intent that cancels any UI matching the given request token id.
*/
@NonNull
- public static Intent createCancelUiIntent(@NonNull IBinder requestToken,
- boolean shouldShowCancellationUi, @NonNull String appPackageName) {
+ public static Intent createCancelUiIntent(@NonNull Context context,
+ @NonNull IBinder requestToken, boolean shouldShowCancellationUi,
+ @NonNull String appPackageName) {
Intent intent = new Intent();
- ComponentName componentName =
- ComponentName.unflattenFromString(
- Resources.getSystem()
- .getString(
- com.android.internal.R.string
- .config_credentialManagerDialogComponent));
- intent.setComponent(componentName);
+ setCredentialSelectorUiComponentName(context, intent);
intent.putExtra(CancelSelectionRequest.EXTRA_CANCEL_UI_REQUEST,
new CancelSelectionRequest(new RequestToken(requestToken), shouldShowCancellationUi,
appPackageName));
diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl
index 851ce2a..19d1029 100644
--- a/core/java/android/hardware/ISensorPrivacyManager.aidl
+++ b/core/java/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
package android.hardware;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
/** @hide */
@@ -48,7 +47,7 @@
void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
- List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+ List<String> getCameraPrivacyAllowlist();
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
int getToggleSensorPrivacyState(int toggleType, int sensor);
@@ -62,6 +61,10 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY)")
boolean isCameraPrivacyEnabled(String packageName);
+ /** @hide */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY)")
+ void setCameraPrivacyAllowlist(in List<String> allowlist);
+
// =============== End of transactions used on native side as well ============================
void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token,
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 6294a8d..4cdaaddd 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -43,7 +43,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Map;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -204,6 +204,8 @@
* Types of state which can exist for the sensor privacy toggle
* @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
public static class StateTypes {
private StateTypes() {}
@@ -217,30 +219,12 @@
*/
public static final int DISABLED = SensorPrivacyIndividualEnabledSensorProto.DISABLED;
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are helpful for driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS;
-
/**
* Constant indicating privacy is enabled except for the automotive driver assistance apps
* which are required by car manufacturer for driving.
*/
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS;
-
- /**
- * Constant indicating privacy is enabled except for the automotive driver assistance apps
- * which are both helpful for driving and also apps required by car manufacturer for
- * driving.
- */
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public static final int AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- SensorPrivacyIndividualEnabledSensorProto.AUTO_DRIVER_ASSISTANCE_APPS;
+ public static final int ENABLED_EXCEPT_ALLOWLISTED_APPS =
+ SensorPrivacyIndividualEnabledSensorProto.ENABLED_EXCEPT_ALLOWLISTED_APPS;
/**
* Types of state which can exist for a sensor privacy toggle
@@ -250,9 +234,7 @@
@IntDef(value = {
ENABLED,
DISABLED,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS
+ ENABLED_EXCEPT_ALLOWLISTED_APPS
})
@Retention(RetentionPolicy.SOURCE)
public @interface StateType {}
@@ -369,9 +351,6 @@
private final ArrayMap<Pair<Integer, OnSensorPrivacyChangedListener>,
OnSensorPrivacyChangedListener> mLegacyToggleListeners = new ArrayMap<>();
- @GuardedBy("mLock")
- private ArrayMap<String, Boolean> mCameraPrivacyAllowlist = null;
-
/** The singleton ISensorPrivacyListener for IPC which will be used to dispatch to local
* listeners */
@NonNull
@@ -397,7 +376,8 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public void onSensorPrivacyStateChanged(int toggleType, int sensor, int state) {
+ public void onSensorPrivacyStateChanged(@ToggleType int toggleType,
+ @Sensors.Sensor int sensor, @StateTypes.StateType int state) {
synchronized (mLock) {
for (int i = 0; i < mToggleListeners.size(); i++) {
OnSensorPrivacyChangedListener listener = mToggleListeners.keyAt(i);
@@ -725,6 +705,8 @@
/**
* Returns sensor privacy state for a specific sensor.
*
+ * @param toggleType The type of toggle to use
+ * @param sensor The sensor to check
* @return int sensor privacy state.
*
* @hide
@@ -741,10 +723,11 @@
}
}
- /**
+ /**
* Returns if camera privacy is enabled for a specific package.
*
- * @return boolean sensor privacy state.
+ * @param packageName The package to check
+ * @return boolean camera privacy state.
*
* @hide
*/
@@ -763,29 +746,41 @@
* Returns camera privacy allowlist.
*
* @return List of automotive driver assistance packages for
- * privacy allowlisting. The returned map includes the package
- * name as key and the value is a Boolean which tells if that package
- * is required by the car manufacturer as mandatory package for driving.
+ * privacy allowlisting.
*
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- public @NonNull Map<String, Boolean> getCameraPrivacyAllowlist() {
+ public @NonNull List<String> getCameraPrivacyAllowlist() {
synchronized (mLock) {
- if (mCameraPrivacyAllowlist == null) {
- mCameraPrivacyAllowlist = new ArrayMap<>();
- try {
- for (CameraPrivacyAllowlistEntry entry :
- mService.getCameraPrivacyAllowlist()) {
- mCameraPrivacyAllowlist.put(entry.packageName, entry.isMandatory);
- }
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ try {
+ return mService.getCameraPrivacyAllowlist();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- return mCameraPrivacyAllowlist;
+ }
+ }
+
+ /**
+ * Sets camera privacy allowlist.
+ *
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.MANAGE_SENSOR_PRIVACY)
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ public void setCameraPrivacyAllowlist(@NonNull List<String> allowlist) {
+ synchronized (mLock) {
+ try {
+ mService.setCameraPrivacyAllowlist(allowlist);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
@@ -867,6 +862,7 @@
/**
* Sets sensor privacy to the specified state for an individual sensor.
*
+ * @param source the source using which the sensor is toggled
* @param sensor the sensor which to change the state for
* @param state the state to which sensor privacy should be set.
*
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index d9d4305..0208fed 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -23,7 +23,6 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.Flags.FLAG_ADD_KEY_AGREEMENT_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
import static android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE;
@@ -1128,8 +1127,6 @@
* @hide
*/
@Override
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public void onAuthenticationAcquired(int acquireInfo) {}
/**
diff --git a/core/java/android/hardware/biometrics/SensorProperties.java b/core/java/android/hardware/biometrics/SensorProperties.java
index 16f71414..3b9cad4 100644
--- a/core/java/android/hardware/biometrics/SensorProperties.java
+++ b/core/java/android/hardware/biometrics/SensorProperties.java
@@ -16,9 +16,6 @@
package android.hardware.biometrics;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.TestApi;
@@ -144,10 +141,8 @@
/**
* @hide
*/
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public SensorProperties(int sensorId, @Strength int sensorStrength,
- @NonNull List<ComponentInfo> componentInfo) {
+ List<ComponentInfo> componentInfo) {
mSensorId = sensorId;
mSensorStrength = sensorStrength;
mComponentInfo = componentInfo;
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 8165d44..3ba8be4 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -28,10 +28,3 @@
bug: "302735104"
}
-flag {
- name: "face_background_authentication"
- namespace: "biometrics_framework"
- description: "Feature flag for allowing face background authentication with USE_BACKGROUND_FACE_AUTHENTICATION."
- bug: "318584190"
-}
-
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 57b437f..dc8f4b4 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3521,7 +3521,7 @@
* <p>When the key is present, only a PRIVATE/YUV output of the specified size is guaranteed
* to be supported by the camera HAL in the secure camera mode. Any other format or
* resolutions might not be supported. Use
- * {@link CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link CameraDevice#isSessionConfigurationSupported }
* API to query if a secure session configuration is supported if the device supports this
* API.</p>
* <p>If this key returns null on a device with SECURE_IMAGE_DATA capability, the application
@@ -5046,18 +5046,18 @@
/**
* <p>The version of the session configuration query
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* API</p>
* <p>The possible values in this key correspond to the values defined in
* android.os.Build.VERSION_CODES. Each version defines a set of feature combinations the
* camera device must reliably report whether they are supported via
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* API. And the version is always less or equal to android.os.Build.VERSION.SDK_INT.</p>
* <p>If set to UPSIDE_DOWN_CAKE, this camera device doesn't support
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }.
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }.
* Calling the method for this camera ID throws an UnsupportedOperationException.</p>
* <p>If set to VANILLA_ICE_CREAM, the application can call
- * {@link android.hardware.camera2.CameraManager#isSessionConfigurationWithParametersSupported }
+ * {@link android.hardware.camera2.CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported }
* to check if the combinations of below features are supported.</p>
* <ul>
* <li>A subset of LIMITED-level device stream combinations.</li>
@@ -6082,11 +6082,11 @@
/**
* <p>Minimum and maximum padding zoom factors supported by this camera device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used for the
+ * android.efv.paddingZoomFactor used for the
* {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
* extension.</p>
* <p>The minimum and maximum padding zoom factors supported by the device for
- * {@link android.hardware.camera2.ExtensionCaptureRequest#EFV_PADDING_ZOOM_FACTOR } used as part of the
+ * android.efv.paddingZoomFactor used as part of the
* {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_EYES_FREE_VIDEOGRAPHY }
* extension feature. This extension specific camera characteristic can be queried using
* {@link android.hardware.camera2.CameraExtensionCharacteristics#get }.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index e24c98e..9fb561b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -911,10 +911,10 @@
* </ul>
* <p>Combinations of logical and physical streams, or physical streams from different
* physical cameras are not guaranteed. However, if the camera device supports
- * {@link CameraManager#isSessionConfigurationWithParametersSupported },
+ * {@link CameraDevice#isSessionConfigurationSupported },
* application must be able to query whether a stream combination involving physical
* streams is supported by calling
- * {@link CameraManager#isSessionConfigurationWithParametersSupported }.</p>
+ * {@link CameraDevice#isSessionConfigurationSupported }.</p>
* <p>Camera application shouldn't assume that there are at most 1 rear camera and 1 front
* camera in the system. For an application that switches between front and back cameras,
* the recommendation is to switch between the first rear camera and the first front
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 9fbe348..f3b7b91 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -260,7 +260,7 @@
* smaller sizes, then the resulting
* {@link android.hardware.camera2.params.SessionConfiguration session configuration} can
* be tested either by calling {@link CameraDevice#createCaptureSession} or
- * {@link CameraManager#isSessionConfigurationWithParametersSupported}.
+ * {@link CameraDeviceSetup#isSessionConfigurationSupported}.
*
* @return non-modifiable ascending list of available sizes.
*/
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 6b2814e..58aafbc 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -778,6 +778,16 @@
*/
float[] getAutoBrightnessLuxLevels(int mode);
+ /**
+ * @return The current brightness setting
+ */
+ float getBrightness();
+
+ /**
+ * @return The brightness value that is used when the device is in doze
+ */
+ float getDozeBrightness();
+
/** Returns whether displayoffload supports the given display state. */
static boolean isSupportedOffloadState(int displayState) {
return Display.isSuspendedState(displayState);
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 1b0a485..210ce2b 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -18,36 +18,28 @@
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
-import static android.Manifest.permission.TEST_BIOMETRIC;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFaceConstants;
-import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricStateListener;
-import android.hardware.biometrics.BiometricTestSession;
import android.hardware.biometrics.CryptoObject;
import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
import android.os.Binder;
import android.os.CancellationSignal;
import android.os.CancellationSignal.OnCancelListener;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IRemoteCallback;
+import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Trace;
@@ -57,22 +49,15 @@
import android.view.Surface;
import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.Executor;
/**
* A class that coordinates access to the face authentication hardware.
- *
- * <p>Please use {@link BiometricPrompt} for face authentication unless the experience must be
- * customized for unique system-level utilities, like the lock screen or ambient background usage.
- *
* @hide
*/
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
-@SystemApi
-@TestApi
@SystemService(Context.FACE_SERVICE)
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
@@ -103,76 +88,81 @@
@Nullable private GenerateChallengeCallback mGenerateChallengeCallback;
private CryptoObject mCryptoObject;
private Face mRemovalFace;
- private Executor mExecutor;
+ private Handler mHandler;
private List<FaceSensorPropertiesInternal> mProps = new ArrayList<>();
private final IFaceServiceReceiver mServiceReceiver = new IFaceServiceReceiver.Stub() {
@Override // binder call
public void onEnrollResult(Face face, int remaining) {
- mExecutor.execute(() -> sendEnrollResult(face, remaining));
+ mHandler.obtainMessage(MSG_ENROLL_RESULT, remaining, 0, face).sendToTarget();
}
@Override // binder call
public void onAcquired(int acquireInfo, int vendorCode) {
- mExecutor.execute(() -> sendAcquiredResult(acquireInfo, vendorCode));
+ mHandler.obtainMessage(MSG_ACQUIRED, acquireInfo, vendorCode).sendToTarget();
}
@Override // binder call
public void onAuthenticationSucceeded(Face face, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendAuthenticatedSucceeded(face, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_SUCCEEDED, userId,
+ isStrongBiometric ? 1 : 0, face).sendToTarget();
}
@Override // binder call
public void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric) {
- mExecutor.execute(() -> sendFaceDetected(sensorId, userId, isStrongBiometric));
+ mHandler.obtainMessage(MSG_FACE_DETECTED, sensorId, userId, isStrongBiometric)
+ .sendToTarget();
}
@Override // binder call
public void onAuthenticationFailed() {
- mExecutor.execute(() -> sendAuthenticatedFailed());
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FAILED).sendToTarget();
}
@Override // binder call
public void onError(int error, int vendorCode) {
- mExecutor.execute(() -> sendErrorResult(error, vendorCode));
+ mHandler.obtainMessage(MSG_ERROR, error, vendorCode).sendToTarget();
}
@Override // binder call
public void onRemoved(Face face, int remaining) {
- mExecutor.execute(() -> {
- sendRemovedResult(face, remaining);
- if (remaining == 0) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
- UserHandle.USER_CURRENT);
- }
- });
+ mHandler.obtainMessage(MSG_REMOVED, remaining, 0, face).sendToTarget();
+ if (remaining == 0) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.FACE_UNLOCK_RE_ENROLL, 0,
+ UserHandle.USER_CURRENT);
+ }
}
@Override
public void onFeatureSet(boolean success, int feature) {
- mExecutor.execute(() -> sendSetFeatureCompleted(success, feature));
+ mHandler.obtainMessage(MSG_SET_FEATURE_COMPLETED, feature, 0, success).sendToTarget();
}
@Override
public void onFeatureGet(boolean success, int[] features, boolean[] featureState) {
- mExecutor.execute(() -> sendGetFeatureCompleted(success, features, featureState));
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = success;
+ args.arg2 = features;
+ args.arg3 = featureState;
+ mHandler.obtainMessage(MSG_GET_FEATURE_COMPLETED, args).sendToTarget();
}
@Override
public void onChallengeGenerated(int sensorId, int userId, long challenge) {
- mExecutor.execute(() -> sendChallengeGenerated(sensorId, userId, challenge));
+ mHandler.obtainMessage(MSG_CHALLENGE_GENERATED, sensorId, userId, challenge)
+ .sendToTarget();
}
@Override
public void onAuthenticationFrame(FaceAuthenticationFrame frame) {
- mExecutor.execute(() -> sendAuthenticationFrame(frame));
+ mHandler.obtainMessage(MSG_AUTHENTICATION_FRAME, frame).sendToTarget();
}
@Override
public void onEnrollmentFrame(FaceEnrollFrame frame) {
- mExecutor.execute(() -> sendEnrollmentFrame(frame));
+ mHandler.obtainMessage(MSG_ENROLLMENT_FRAME, frame).sendToTarget();
}
};
@@ -185,7 +175,7 @@
if (mService == null) {
Slog.v(TAG, "FaceAuthenticationManagerService was null");
}
- mExecutor = context.getMainExecutor();
+ mHandler = new MyHandler(context);
if (context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL)
== PackageManager.PERMISSION_GRANTED) {
addAuthenticatorsRegisteredCallback(new IFaceAuthenticatorsRegisteredCallback.Stub() {
@@ -199,16 +189,18 @@
}
/**
- * Returns an {@link Executor} for the given {@link Handler} or the main {@link Executor} if
- * {@code handler} is {@code null}.
+ * Use the provided handler thread for events.
*/
- private @NonNull Executor createExecutorForHandlerIfNeeded(@Nullable Handler handler) {
- return handler != null ? new HandlerExecutor(handler) : mContext.getMainExecutor();
+ private void useHandler(Handler handler) {
+ if (handler != null) {
+ mHandler = new MyHandler(handler.getLooper());
+ } else if (mHandler.getLooper() != mContext.getMainLooper()) {
+ mHandler = new MyHandler(mContext.getMainLooper());
+ }
}
/**
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FaceAuthenticateOptions)}.
- * @hide
*/
@Deprecated
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -220,22 +212,17 @@
}
/**
- * Request authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
+ * Request authentication. This call operates the face recognition hardware and starts capturing images.
* It terminates when
* {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
* {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult)} is called, at
* which point the object is no longer valid. The operation can be canceled by using the
- * provided {@code cancel} object.
+ * provided cancel object.
*
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed
+ * @param crypto object associated with the call or null if none required
+ * @param cancel an object that can be used to cancel authentication
* @param callback an object to receive authentication events
- * @param handler an optional handler to handle callback events or {@code null} to obtain main
- * {@link Executor} from {@link Context}
+ * @param handler an optional handler to handle callback events
* @param options additional options to customize this request
* @throws IllegalArgumentException if the crypto operation is not supported or is not backed
* by
@@ -248,14 +235,6 @@
public void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
@NonNull AuthenticationCallback callback, @Nullable Handler handler,
@NonNull FaceAuthenticateOptions options) {
- authenticate(crypto, cancel, callback, createExecutorForHandlerIfNeeded(handler),
- options, false /* allowBackgroundAuthentication */);
- }
-
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- private void authenticate(@Nullable CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull AuthenticationCallback callback, @NonNull Executor executor,
- @NonNull FaceAuthenticateOptions options, boolean allowBackgroundAuthentication) {
if (callback == null) {
throw new IllegalArgumentException("Must supply an authentication callback");
}
@@ -270,15 +249,13 @@
if (mService != null) {
try {
- mExecutor = executor;
+ useHandler(handler);
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- final long authId = allowBackgroundAuthentication
- ? mService.authenticateInBackground(
- mToken, operationId, mServiceReceiver, options)
- : mService.authenticate(mToken, operationId, mServiceReceiver, options);
+ final long authId = mService.authenticate(
+ mToken, operationId, mServiceReceiver, options);
if (cancel != null) {
cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
}
@@ -296,67 +273,6 @@
}
/**
- * Request background face authentication.
- *
- * <p>This call operates the face recognition hardware and starts capturing images.
- * It terminates when
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationError(int, CharSequence)} or
- * {@link BiometricPrompt.AuthenticationCallback#onAuthenticationSucceeded(
- * BiometricPrompt.AuthenticationResult)} is called, at which point the object is no longer
- * valid. The operation can be canceled by using the provided cancel object.
- *
- * <p>See {@link BiometricPrompt#authenticate} for more details. Please use
- * {@link BiometricPrompt} for face authentication unless the experience must be customized for
- * unique system-level utilities, like the lock screen or ambient background usage.
- *
- * @param executor the specified {@link Executor} to handle callback events; if {@code null},
- * the callback will be executed on the main {@link Executor}.
- * @param crypto the cryptographic operations to use for authentication or {@code null} if
- * none required.
- * @param cancel an object that can be used to cancel authentication or {@code null} if not
- * needed.
- * @param callback an object to receive authentication events.
- * @throws IllegalArgumentException if the crypto operation is not supported or is not backed
- * by
- * <a href="{@docRoot}training/articles/keystore.html">Android
- * Keystore facility</a>.
- * @hide
- */
- @RequiresPermission(USE_BACKGROUND_FACE_AUTHENTICATION)
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
- public void authenticateInBackground(@Nullable Executor executor,
- @Nullable BiometricPrompt.CryptoObject crypto, @Nullable CancellationSignal cancel,
- @NonNull BiometricPrompt.AuthenticationCallback callback) {
- authenticate(crypto, cancel, new AuthenticationCallback() {
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- callback.onAuthenticationError(errorCode, errString);
- }
-
- @Override
- public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
- callback.onAuthenticationHelp(helpCode, helpString);
- }
-
- @Override
- public void onAuthenticationSucceeded(AuthenticationResult result) {
- callback.onAuthenticationSucceeded(
- new BiometricPrompt.AuthenticationResult(
- crypto,
- BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC));
- }
-
- @Override
- public void onAuthenticationFailed() {
- callback.onAuthenticationFailed();
- }
- }, executor == null ? mContext.getMainExecutor() : executor,
- new FaceAuthenticateOptions.Builder().build(),
- true /* allowBackgroundAuthentication */);
- }
-
- /**
* Uses the face hardware to detect for the presence of a face, without giving details about
* accept/reject/lockout.
* @hide
@@ -714,14 +630,12 @@
}
/**
- * Determine if there are enrolled {@link Face} templates.
+ * Determine if there is a face enrolled.
*
- * @return {@code true} if there are enrolled {@link Face} templates, {@code false} otherwise
+ * @return true if a face is enrolled, false otherwise
* @hide
*/
- @RequiresPermission(anyOf = {USE_BIOMETRIC_INTERNAL, USE_BACKGROUND_FACE_AUTHENTICATION})
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- @SystemApi
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
public boolean hasEnrolledTemplates() {
return hasEnrolledTemplates(UserHandle.myUserId());
}
@@ -784,8 +698,6 @@
* @hide
*/
@NonNull
- @TestApi
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public List<FaceSensorProperties> getSensorProperties() {
final List<FaceSensorProperties> properties = new ArrayList<>();
final List<FaceSensorPropertiesInternal> internalProperties
@@ -888,7 +800,7 @@
PowerManager.PARTIAL_WAKE_LOCK,
"faceLockoutResetCallback");
wakeLock.acquire();
- mExecutor.execute(() -> {
+ mHandler.post(() -> {
try {
callback.onLockoutReset(sensorId);
} finally {
@@ -1358,6 +1270,70 @@
}
}
+ private class MyHandler extends Handler {
+ private MyHandler(Context context) {
+ super(context.getMainLooper());
+ }
+
+ private MyHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(android.os.Message msg) {
+ Trace.beginSection("FaceManager#handleMessage: " + Integer.toString(msg.what));
+ switch (msg.what) {
+ case MSG_ENROLL_RESULT:
+ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_ACQUIRED:
+ sendAcquiredResult(msg.arg1 /* acquire info */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_AUTHENTICATION_SUCCEEDED:
+ sendAuthenticatedSucceeded((Face) msg.obj, msg.arg1 /* userId */,
+ msg.arg2 == 1 /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FAILED:
+ sendAuthenticatedFailed();
+ break;
+ case MSG_ERROR:
+ sendErrorResult(msg.arg1 /* errMsgId */, msg.arg2 /* vendorCode */);
+ break;
+ case MSG_REMOVED:
+ sendRemovedResult((Face) msg.obj, msg.arg1 /* remaining */);
+ break;
+ case MSG_SET_FEATURE_COMPLETED:
+ sendSetFeatureCompleted((boolean) msg.obj /* success */,
+ msg.arg1 /* feature */);
+ break;
+ case MSG_GET_FEATURE_COMPLETED:
+ SomeArgs args = (SomeArgs) msg.obj;
+ sendGetFeatureCompleted((boolean) args.arg1 /* success */,
+ (int[]) args.arg2 /* features */,
+ (boolean[]) args.arg3 /* featureState */);
+ args.recycle();
+ break;
+ case MSG_CHALLENGE_GENERATED:
+ sendChallengeGenerated(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (long) msg.obj /* challenge */);
+ break;
+ case MSG_FACE_DETECTED:
+ sendFaceDetected(msg.arg1 /* sensorId */, msg.arg2 /* userId */,
+ (boolean) msg.obj /* isStrongBiometric */);
+ break;
+ case MSG_AUTHENTICATION_FRAME:
+ sendAuthenticationFrame((FaceAuthenticationFrame) msg.obj /* frame */);
+ break;
+ case MSG_ENROLLMENT_FRAME:
+ sendEnrollmentFrame((FaceEnrollFrame) msg.obj /* frame */);
+ break;
+ default:
+ Slog.w(TAG, "Unknown message: " + msg.what);
+ }
+ Trace.endSection();
+ }
+ }
+
private void sendSetFeatureCompleted(boolean success, int feature) {
if (mSetFeatureCallback == null) {
return;
@@ -1634,23 +1610,4 @@
Slog.w(TAG, "Unknown enrollment acquired message: " + acquireInfo + ", " + vendorCode);
return null;
}
-
- /**
- * Retrieves a test session for FaceManager.
- *
- * @hide
- */
- @TestApi
- @NonNull
- @RequiresPermission(TEST_BIOMETRIC)
- @FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- public BiometricTestSession createTestSession(int sensorId) {
- try {
- return new BiometricTestSession(mContext, sensorId,
- (context, sensorId1, callback) -> mService
- .createTestSession(sensorId1, callback, context.getOpPackageName()));
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/hardware/face/FaceSensorProperties.java b/core/java/android/hardware/face/FaceSensorProperties.java
index a1ddb0e..f613127 100644
--- a/core/java/android/hardware/face/FaceSensorProperties.java
+++ b/core/java/android/hardware/face/FaceSensorProperties.java
@@ -16,12 +16,8 @@
package android.hardware.face;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
-
-import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.TestApi;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
@@ -34,8 +30,6 @@
* Container for face sensor properties.
* @hide
*/
-@TestApi
-@FlaggedApi(FLAG_FACE_BACKGROUND_AUTHENTICATION)
public class FaceSensorProperties extends SensorProperties {
/**
* @hide
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index 6515312..553d9f7 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -39,7 +39,7 @@
interface IFaceService {
// Creates a test session with the specified sensorId
- @EnforcePermission("TEST_BIOMETRIC")
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
ITestSession createTestSession(int sensorId, ITestSessionCallback callback, String opPackageName);
// Requests a proto dump of the specified sensor
@@ -47,7 +47,7 @@
byte[] dumpSensorServiceStateProto(int sensorId, boolean clearSchedulerBuffer);
// Retrieve static sensor properties for all face sensors
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(String opPackageName);
// Retrieve static sensor properties for the specified sensor
@@ -59,11 +59,6 @@
long authenticate(IBinder token, long operationId, IFaceServiceReceiver receiver,
in FaceAuthenticateOptions options);
- // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
- @EnforcePermission("USE_BACKGROUND_FACE_AUTHENTICATION")
- long authenticateInBackground(IBinder token, long operationId, IFaceServiceReceiver receiver,
- in FaceAuthenticateOptions options);
-
// Uses the face hardware to detect for the presence of a face, without giving details
// about accept/reject/lockout. A requestId is returned that can be used to cancel this
// operation.
@@ -138,7 +133,7 @@
void revokeChallenge(IBinder token, int sensorId, int userId, String opPackageName, long challenge);
// Determine if a user has at least one enrolled face
- @EnforcePermission(anyOf = {"USE_BIOMETRIC_INTERNAL", "USE_BACKGROUND_FACE_AUTHENTICATION"})
+ @EnforcePermission("USE_BIOMETRIC_INTERNAL")
boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName);
// Return the LockoutTracker status for the specified user
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 81e321d..b0f69f5 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -83,7 +83,8 @@
/**
* A class that coordinates access to the fingerprint hardware.
- * @deprecated See {@link BiometricPrompt} which shows a system-provided dialog upon starting
+ *
+ * @removed See {@link BiometricPrompt} which shows a system-provided dialog upon starting
* authentication. In a world where devices may have different types of biometric authentication,
* it's much more realistic to have a system-provided authentication dialog since the method may
* vary by vendor/device.
@@ -94,7 +95,6 @@
@RequiresFeature(PackageManager.FEATURE_FINGERPRINT)
public class FingerprintManager implements BiometricAuthenticator, BiometricFingerprintConstants {
private static final String TAG = "FingerprintManager";
- private static final boolean DEBUG = true;
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -196,6 +196,7 @@
/**
* Retrieves a test session for FingerprintManager.
+ *
* @hide
*/
@TestApi
@@ -254,9 +255,10 @@
}
/**
- * A wrapper class for the crypto objects supported by FingerprintManager. Currently the
+ * A wrapper class for the crypto objects supported by FingerprintManager. Currently, the
* framework supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}
*/
@Deprecated
public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
@@ -330,7 +332,8 @@
/**
* Container for callback data from {@link FingerprintManager#authenticate(CryptoObject,
* CancellationSignal, int, AuthenticationCallback, Handler)}.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationResult}
*/
@Deprecated
public static class AuthenticationResult {
@@ -392,7 +395,8 @@
* FingerprintManager#authenticate(CryptoObject, CancellationSignal,
* int, AuthenticationCallback, Handler) } must provide an implementation of this for listening to
* fingerprint events.
- * @deprecated See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
+ *
+ * @removed See {@link android.hardware.biometrics.BiometricPrompt.AuthenticationCallback}
*/
@Deprecated
public static abstract class AuthenticationCallback
@@ -455,6 +459,7 @@
/**
* Callback structure provided for {@link #detectFingerprint(CancellationSignal,
* FingerprintDetectionCallback, int, Surface)}.
+ *
* @hide
*/
public interface FingerprintDetectionCallback {
@@ -608,7 +613,8 @@
* by <a href="{@docRoot}training/articles/keystore.html">Android Keystore
* facility</a>.
* @throws IllegalStateException if the crypto primitive is not initialized.
- * @deprecated See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
+ *
+ * @removed See {@link BiometricPrompt#authenticate(CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)} and {@link BiometricPrompt#authenticate(
* BiometricPrompt.CryptoObject, CancellationSignal, Executor,
* BiometricPrompt.AuthenticationCallback)}
@@ -623,6 +629,7 @@
/**
* Per-user version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+ *
* @hide
*/
@Deprecated
@@ -635,6 +642,7 @@
/**
* Per-user and per-sensor version of authenticate.
* @deprecated use {@link #authenticate(CryptoObject, CancellationSignal, AuthenticationCallback, Handler, FingerprintAuthenticateOptions)}.
+ *
* @hide
*/
@Deprecated
@@ -651,6 +659,7 @@
/**
* Version of authenticate with additional options.
+ *
* @hide
*/
@RequiresPermission(anyOf = {USE_BIOMETRIC, USE_FINGERPRINT})
@@ -698,6 +707,7 @@
/**
* Uses the fingerprint hardware to detect for the presence of a finger, without giving details
* about accept/reject/lockout.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -740,6 +750,7 @@
* @param callback an object to receive enrollment events
* @param shouldLogMetrics a flag that indicates if enrollment failure/success metrics
* should be logged.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -810,6 +821,7 @@
/**
* Same as {@link #generateChallenge(int, GenerateChallengeCallback)}, but assumes the first
* enumerated sensor.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -824,6 +836,7 @@
/**
* Revokes the specified challenge.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -849,6 +862,7 @@
* @param sensorId Sensor ID that this operation takes effect for
* @param userId User ID that this operation takes effect for.
* @param hardwareAuthToken An opaque token returned by password confirmation.
+ *
* @hide
*/
@RequiresPermission(RESET_FINGERPRINT_LOCKOUT)
@@ -886,6 +900,7 @@
/**
* Removes all fingerprint templates for the given user.
+ *
* @hide
*/
@RequiresPermission(MANAGE_FINGERPRINT)
@@ -1005,6 +1020,7 @@
/**
* Forwards BiometricStateListener to FingerprintService
* @param listener new BiometricStateListener being added
+ *
* @hide
*/
public void registerBiometricStateListener(@NonNull BiometricStateListener listener) {
@@ -1156,7 +1172,8 @@
}
/**
- * This is triggered by SideFpsEventHandler
+ * This is triggered by SideFpsEventHandler.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1169,7 +1186,8 @@
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
- * @deprecated See {@link BiometricPrompt} and
+ *
+ * @removed See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_NO_FINGERPRINTS}
*/
@Deprecated
@@ -1203,7 +1221,8 @@
* Determine if fingerprint hardware is present and functional.
*
* @return true if hardware is present and functional, false otherwise.
- * @deprecated See {@link BiometricPrompt} and
+ *
+ * @removed See {@link BiometricPrompt} and
* {@link FingerprintManager#FINGERPRINT_ERROR_HW_UNAVAILABLE}
*/
@Deprecated
@@ -1229,6 +1248,7 @@
/**
* Get statically configured sensor properties.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1247,6 +1267,7 @@
/**
* Returns whether the device has a power button fingerprint sensor.
* @return boolean indicating whether power button is fingerprint sensor
+ *
* @hide
*/
public boolean isPowerbuttonFps() {
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1f54959..2816f77 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -27,6 +27,7 @@
import android.hardware.input.IKeyboardBacklightState;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
import android.hardware.input.IInputSensorEventListener;
@@ -120,8 +121,9 @@
String keyboardLayoutDescriptor);
// New Keyboard layout config APIs
- String getKeyboardLayoutForInputDevice(in InputDeviceIdentifier identifier, int userId,
- in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
+ KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ in InputDeviceIdentifier identifier, int userId, in InputMethodInfo imeInfo,
+ in InputMethodSubtype imeSubtype);
@EnforcePermission("SET_KEYBOARD_LAYOUT")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 744dfae9..a1242fb 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -784,10 +784,10 @@
*
* @hide
*/
- @Nullable
- public String getKeyboardLayoutForInputDevice(@NonNull InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ @NonNull
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ @NonNull InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
try {
return mIm.getKeyboardLayoutForInputDevice(identifier, userId, imeInfo, imeSubtype);
} catch (RemoteException ex) {
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
similarity index 67%
copy from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
copy to core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
index 838e41e..13be2ff 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-package android.hardware;
+package android.hardware.input;
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
-
+parcelable KeyboardLayoutSelectionResult;
diff --git a/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
new file mode 100644
index 0000000..5a1c947
--- /dev/null
+++ b/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcelable;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the selected layout and the selection criteria when the caller calls
+ * {@link InputManager#getKeyboardLayoutForInputDevice(InputDeviceIdentifier, int, InputMethodInfo,
+ * InputMethodSubtype)}
+ *
+ * @hide
+ */
+
+@DataClass(genParcelable = true, genToString = true, genEqualsHashCode = true)
+public final class KeyboardLayoutSelectionResult implements Parcelable {
+ @Nullable
+ private final String mLayoutDescriptor;
+
+ /** Unspecified layout selection criteria */
+ public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
+ /** Manual selection by user */
+ public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
+
+ /** Auto-detection based on device provided language tag and layout type */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
+
+ /** Auto-detection based on IME provided language tag and layout type */
+ public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
+
+ /** Default selection */
+ public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
+
+ /** Failed layout selection */
+ public static final KeyboardLayoutSelectionResult FAILED = new KeyboardLayoutSelectionResult(
+ null, LAYOUT_SELECTION_CRITERIA_UNSPECIFIED);
+
+ @LayoutSelectionCriteria
+ private final int mSelectionCriteria;
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @IntDef(prefix = "LAYOUT_SELECTION_CRITERIA_", value = {
+ LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
+ LAYOUT_SELECTION_CRITERIA_USER,
+ LAYOUT_SELECTION_CRITERIA_DEVICE,
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ LAYOUT_SELECTION_CRITERIA_DEFAULT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface LayoutSelectionCriteria {}
+
+ @DataClass.Generated.Member
+ public static String layoutSelectionCriteriaToString(@LayoutSelectionCriteria int value) {
+ switch (value) {
+ case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+ return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
+ case LAYOUT_SELECTION_CRITERIA_USER:
+ return "LAYOUT_SELECTION_CRITERIA_USER";
+ case LAYOUT_SELECTION_CRITERIA_DEVICE:
+ return "LAYOUT_SELECTION_CRITERIA_DEVICE";
+ case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
+ return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
+ case LAYOUT_SELECTION_CRITERIA_DEFAULT:
+ return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ @DataClass.Generated.Member
+ public KeyboardLayoutSelectionResult(
+ @Nullable String layoutDescriptor,
+ @LayoutSelectionCriteria int selectionCriteria) {
+ this.mLayoutDescriptor = layoutDescriptor;
+ this.mSelectionCriteria = selectionCriteria;
+
+ if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+ throw new java.lang.IllegalArgumentException(
+ "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+ + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+ + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+ + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @Nullable String getLayoutDescriptor() {
+ return mLayoutDescriptor;
+ }
+
+ @DataClass.Generated.Member
+ public @LayoutSelectionCriteria int getSelectionCriteria() {
+ return mSelectionCriteria;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "KeyboardLayoutSelectionResult { " +
+ "layoutDescriptor = " + mLayoutDescriptor + ", " +
+ "selectionCriteria = " + layoutSelectionCriteriaToString(mSelectionCriteria) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(KeyboardLayoutSelectionResult other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ KeyboardLayoutSelectionResult that = (KeyboardLayoutSelectionResult) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mLayoutDescriptor, that.mLayoutDescriptor)
+ && mSelectionCriteria == that.mSelectionCriteria;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mLayoutDescriptor);
+ _hash = 31 * _hash + mSelectionCriteria;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mLayoutDescriptor != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mLayoutDescriptor != null) dest.writeString(mLayoutDescriptor);
+ dest.writeInt(mSelectionCriteria);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ KeyboardLayoutSelectionResult(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String layoutDescriptor = (flg & 0x1) == 0 ? null : in.readString();
+ int selectionCriteria = in.readInt();
+
+ this.mLayoutDescriptor = layoutDescriptor;
+ this.mSelectionCriteria = selectionCriteria;
+
+ if (!(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_UNSPECIFIED)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_USER)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEVICE)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD)
+ && !(mSelectionCriteria == LAYOUT_SELECTION_CRITERIA_DEFAULT)) {
+ throw new java.lang.IllegalArgumentException(
+ "selectionCriteria was " + mSelectionCriteria + " but must be one of: "
+ + "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED(" + LAYOUT_SELECTION_CRITERIA_UNSPECIFIED + "), "
+ + "LAYOUT_SELECTION_CRITERIA_USER(" + LAYOUT_SELECTION_CRITERIA_USER + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEVICE(" + LAYOUT_SELECTION_CRITERIA_DEVICE + "), "
+ + "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD(" + LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD + "), "
+ + "LAYOUT_SELECTION_CRITERIA_DEFAULT(" + LAYOUT_SELECTION_CRITERIA_DEFAULT + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<KeyboardLayoutSelectionResult> CREATOR
+ = new Parcelable.Creator<KeyboardLayoutSelectionResult>() {
+ @Override
+ public KeyboardLayoutSelectionResult[] newArray(int size) {
+ return new KeyboardLayoutSelectionResult[size];
+ }
+
+ @Override
+ public KeyboardLayoutSelectionResult createFromParcel(@NonNull android.os.Parcel in) {
+ return new KeyboardLayoutSelectionResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1709568115865L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardLayoutSelectionResult.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mLayoutDescriptor\npublic static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED\npublic static final int LAYOUT_SELECTION_CRITERIA_USER\npublic static final int LAYOUT_SELECTION_CRITERIA_DEVICE\npublic static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD\npublic static final int LAYOUT_SELECTION_CRITERIA_DEFAULT\npublic static final android.hardware.input.KeyboardLayoutSelectionResult FAILED\nprivate final @android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria int mSelectionCriteria\nclass KeyboardLayoutSelectionResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genToString=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 800ba6d..23d6007 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -85,6 +85,7 @@
boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
boolean isRestricted(int userId);
boolean canHaveRestrictedProfile(int userId);
+ boolean canAddPrivateProfile(int userId);
int getUserSerialNumber(int userId);
int getUserHandle(int userSerialNumber);
int getUserRestrictionSource(String restrictionKey, int userId);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 9757a10..fdaa0b4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2353,6 +2353,17 @@
public static final int USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS = 7;
/**
+ * Indicates user operation failed because user is disabled on the device.
+ * @hide
+ */
+ public static final int USER_OPERATION_ERROR_DISABLED_USER = 8;
+ /**
+ * Indicates user operation failed because user is disabled on the device.
+ * @hide
+ */
+ public static final int USER_OPERATION_ERROR_PRIVATE_PROFILE = 9;
+
+ /**
* Result returned from various user operations.
*
* @hide
@@ -2366,7 +2377,9 @@
USER_OPERATION_ERROR_CURRENT_USER,
USER_OPERATION_ERROR_LOW_STORAGE,
USER_OPERATION_ERROR_MAX_USERS,
- USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS
+ USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS,
+ USER_OPERATION_ERROR_DISABLED_USER,
+ USER_OPERATION_ERROR_PRIVATE_PROFILE,
})
public @interface UserOperationResult {}
@@ -2563,6 +2576,17 @@
}
/**
+ * Returns whether the device supports Private Profile
+ * @hide
+ */
+ public static boolean isPrivateProfileEnabled() {
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+ return !ActivityManager.isLowRamDeviceStatic();
+ }
+ return true;
+ }
+
+ /**
* Returns whether multiple admins are enabled on the device
* @hide
*/
@@ -3155,6 +3179,27 @@
}
/**
+ * Checks if it's possible to add a private profile to the context user
+ * @return whether the context user can add a private profile.
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS},
+ conditional = true)
+ @UserHandleAware
+ public boolean canAddPrivateProfile() {
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()) {
+ try {
+ return mService.canAddPrivateProfile(mUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return true;
+ }
+
+ /**
* Returns whether the context user has at least one restricted profile associated with it.
* @return whether the user has a restricted profile associated with it
* @hide
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index abfa4e3..d9400ac 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -1,4 +1,5 @@
package: "android.os"
+container: "system"
flag {
name: "android_os_build_vanilla_ice_cream"
@@ -40,6 +41,7 @@
namespace: "profile_experiences"
description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
bug: "299069460"
+ is_exported: true
}
flag {
diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java
index a14a2c7..555a120 100644
--- a/core/java/android/os/vibrator/VibrationConfig.java
+++ b/core/java/android/os/vibrator/VibrationConfig.java
@@ -70,6 +70,8 @@
private final boolean mDefaultKeyboardVibrationEnabled;
+ private final boolean mHasFixedKeyboardAmplitude;
+
/** @hide */
public VibrationConfig(@Nullable Resources resources) {
mHapticChannelMaxVibrationAmplitude = loadFloat(resources,
@@ -87,6 +89,8 @@
com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger, false);
mDefaultKeyboardVibrationEnabled = loadBoolean(resources,
com.android.internal.R.bool.config_defaultKeyboardVibrationEnabled, true);
+ mHasFixedKeyboardAmplitude = loadFloat(resources,
+ com.android.internal.R.dimen.config_keyboardHapticFeedbackFixedAmplitude, -1) > 0;
mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources,
com.android.internal.R.integer.config_defaultAlarmVibrationIntensity);
@@ -197,6 +201,14 @@
return mDefaultKeyboardVibrationEnabled;
}
+ /**
+ * Whether the device has a fixed amplitude for keyboard.
+ * @hide
+ */
+ public boolean hasFixedKeyboardAmplitude() {
+ return mHasFixedKeyboardAmplitude;
+ }
+
/** Get the default vibration intensity for given usage. */
@VibrationIntensity
public int getDefaultVibrationIntensity(@VibrationAttributes.Usage int usage) {
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index fa9f03d..3c7692d 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1788,6 +1788,9 @@
/**
* Gets the permission states for requested package and persistent device.
+ * <p>
+ * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+ * exact permission states for the requested device.
*
* @param packageName name of the package you are checking against
* @param persistentDeviceId id of the persistent device you are checking against
@@ -1797,7 +1800,11 @@
*/
@SystemApi
@NonNull
- @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+ android.Manifest.permission.GET_RUNTIME_PERMISSIONS
+ })
@FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
public Map<String, PermissionState> getAllPermissionStates(@NonNull String packageName,
@NonNull String persistentDeviceId) {
@@ -2073,5 +2080,29 @@
return new PermissionState[size];
}
};
+
+ /** @hide */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PermissionState that = (PermissionState) o;
+ return mGranted == that.mGranted && mFlags == that.mFlags;
+ }
+
+ /** @hide */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mGranted, mFlags);
+ }
+
+ /** @hide */
+ @Override
+ public String toString() {
+ return "PermissionState{"
+ + "mGranted=" + mGranted
+ + ", mFlags=" + mFlags
+ + '}';
+ }
}
}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index de7008b..dc782d4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -138,3 +138,11 @@
bug: "325356776"
}
+flag {
+ name: "runtime_permission_appops_mapping_enabled"
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "Use runtime permission state to determine appop state"
+ bug: "266164193"
+}
+
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 51585af..e26dc73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3583,6 +3583,12 @@
+ " and user:" + userHandle
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(name);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
@@ -3805,6 +3811,12 @@
+ " in package:" + cr.getPackageName()
+ " with index:" + index);
}
+ // Always make sure to close any pre-existing tracker before
+ // replacing it, to prevent memory leaks
+ var oldTracker = mGenerationTrackers.get(prefix);
+ if (oldTracker != null) {
+ oldTracker.destroy();
+ }
mGenerationTrackers.put(prefix,
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
@@ -11576,6 +11588,15 @@
"extra_low_power_warning_acknowledged";
/**
+ * Whether the emergency thermal alert would be disabled
+ * (0: default) or not (1).
+ *
+ * @hide
+ */
+ public static final String EMERGENCY_THERMAL_ALERT_DISABLED =
+ "emergency_thermal_alert_disabled";
+
+ /**
* 0 (default) Auto battery saver suggestion has not been suppressed. 1) it has been
* suppressed.
* @hide
@@ -19983,6 +20004,13 @@
@Readable
public static final String CONSISTENT_NOTIFICATION_BLOCKING_ENABLED =
"consistent_notification_blocking_enabled";
+
+ /**
+ * Whether the Auto Bedtime Mode experience is enabled.
+ *
+ * @hide
+ */
+ public static final String AUTO_BEDTIME_MODE = "auto_bedtime_mode";
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 7658af5..bd9ab86 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.annotation.UiThread;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -468,6 +469,7 @@
* object as well as its identifying information (tag and id) and source
* (package name).
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
@@ -481,6 +483,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications, including the newly posted one.
*/
+ @UiThread
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
@@ -499,6 +502,7 @@
* and source (package name) used to post the {@link android.app.Notification} that
* was just removed.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn) {
// optional
}
@@ -520,6 +524,7 @@
* for active notifications.
*
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationRemoved(sbn);
}
@@ -541,6 +546,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
@NotificationCancelReason int reason) {
onNotificationRemoved(sbn, rankingMap);
@@ -552,6 +558,7 @@
*
* @hide
*/
+ @UiThread
@SystemApi
public void onNotificationRemoved(@NonNull StatusBarNotification sbn,
@NonNull RankingMap rankingMap, @NonNull NotificationStats stats, int reason) {
@@ -563,6 +570,7 @@
* the notification manager. You are safe to call {@link #getActiveNotifications()}
* at this time.
*/
+ @UiThread
public void onListenerConnected() {
// optional
}
@@ -572,6 +580,7 @@
* notification manager.You will not receive any events after this call, and may only
* call {@link #requestRebind(ComponentName)} at this time.
*/
+ @UiThread
public void onListenerDisconnected() {
// optional
}
@@ -582,6 +591,7 @@
* @param rankingMap The current ranking map that can be used to retrieve ranking information
* for active notifications.
*/
+ @UiThread
public void onNotificationRankingUpdate(RankingMap rankingMap) {
// optional
}
@@ -592,6 +602,7 @@
*
* @param hints The current {@link #getCurrentListenerHints() listener hints}.
*/
+ @UiThread
public void onListenerHintsChanged(int hints) {
// optional
}
@@ -603,6 +614,7 @@
* @param hideSilentStatusIcons whether or not status bar icons should be hidden for silent
* notifications
*/
+ @UiThread
public void onSilentStatusBarIconsVisibilityChanged(boolean hideSilentStatusIcons) {
// optional
}
@@ -620,6 +632,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelModified(String pkg, UserHandle user,
NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -638,6 +651,7 @@
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_UPDATED},
* {@link #NOTIFICATION_CHANNEL_OR_GROUP_DELETED}.
*/
+ @UiThread
public void onNotificationChannelGroupModified(String pkg, UserHandle user,
NotificationChannelGroup group, @ChannelOrGroupModificationTypes int modificationType) {
// optional
@@ -650,6 +664,7 @@
* @param interruptionFilter The current
* {@link #getCurrentInterruptionFilter() interruption filter}.
*/
+ @UiThread
public void onInterruptionFilterChanged(int interruptionFilter) {
// optional
}
@@ -1197,6 +1212,11 @@
* <p>
* Listen for updates using {@link #onInterruptionFilterChanged(int)}.
*
+ * <p>Apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above (with some
+ * exceptions, such as companion device managers) cannot modify the global interruption filter.
+ * Calling this method will instead activate or deactivate an
+ * {@link android.app.AutomaticZenRule} associated to the app.
+ *
* <p>The service should wait for the {@link #onListenerConnected()} event
* before performing this operation.
*
diff --git a/core/java/android/service/notification/ZenAdapters.java b/core/java/android/service/notification/ZenAdapters.java
new file mode 100644
index 0000000..b249815
--- /dev/null
+++ b/core/java/android/service/notification/ZenAdapters.java
@@ -0,0 +1,135 @@
+/*
+ * 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 android.service.notification;
+
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.app.NotificationManager.Policy;
+
+/**
+ * Converters between different Zen representations.
+ * @hide
+ */
+public class ZenAdapters {
+
+ /** Maps {@link Policy} to {@link ZenPolicy}. */
+ @NonNull
+ public static ZenPolicy notificationPolicyToZenPolicy(@NonNull Policy policy) {
+ ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+ .allowAlarms(policy.allowAlarms())
+ .allowCalls(
+ policy.allowCalls()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowCallsFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowConversations(
+ policy.allowConversations()
+ ? notificationPolicyConversationSendersToZenPolicy(
+ policy.allowConversationsFrom())
+ : ZenPolicy.CONVERSATION_SENDERS_NONE)
+ .allowEvents(policy.allowEvents())
+ .allowMedia(policy.allowMedia())
+ .allowMessages(
+ policy.allowMessages()
+ ? notificationPolicySendersToZenPolicyPeopleType(
+ policy.allowMessagesFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowReminders(policy.allowReminders())
+ .allowRepeatCallers(policy.allowRepeatCallers())
+ .allowSystem(policy.allowSystem());
+
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ zenPolicyBuilder.showBadges(policy.showBadges())
+ .showFullScreenIntent(policy.showFullScreenIntents())
+ .showInAmbientDisplay(policy.showAmbient())
+ .showInNotificationList(policy.showInNotificationList())
+ .showLights(policy.showLights())
+ .showPeeking(policy.showPeeking())
+ .showStatusBarIcons(policy.showStatusBarIcons());
+ }
+
+ if (Flags.modesApi()) {
+ zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
+ }
+
+ return zenPolicyBuilder.build();
+ }
+
+ /** Maps {@link ZenPolicy.PeopleType} enum to {@link Policy.PrioritySenders}. */
+ @Policy.PrioritySenders
+ public static int zenPolicyPeopleTypeToNotificationPolicySenders(
+ @ZenPolicy.PeopleType int zpPeopleType, @Policy.PrioritySenders int defaultResult) {
+ switch (zpPeopleType) {
+ case ZenPolicy.PEOPLE_TYPE_ANYONE:
+ return Policy.PRIORITY_SENDERS_ANY;
+ case ZenPolicy.PEOPLE_TYPE_CONTACTS:
+ return Policy.PRIORITY_SENDERS_CONTACTS;
+ case ZenPolicy.PEOPLE_TYPE_STARRED:
+ return Policy.PRIORITY_SENDERS_STARRED;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.PrioritySenders} enum to {@link ZenPolicy.PeopleType}. */
+ @ZenPolicy.PeopleType
+ public static int notificationPolicySendersToZenPolicyPeopleType(
+ @Policy.PrioritySenders int npPrioritySenders) {
+ switch (npPrioritySenders) {
+ case Policy.PRIORITY_SENDERS_ANY:
+ return ZenPolicy.PEOPLE_TYPE_ANYONE;
+ case Policy.PRIORITY_SENDERS_CONTACTS:
+ return ZenPolicy.PEOPLE_TYPE_CONTACTS;
+ case Policy.PRIORITY_SENDERS_STARRED:
+ default:
+ return ZenPolicy.PEOPLE_TYPE_STARRED;
+ }
+ }
+
+ /** Maps {@link ZenPolicy.ConversationSenders} enum to {@link Policy.ConversationSenders}. */
+ @Policy.ConversationSenders
+ public static int zenPolicyConversationSendersToNotificationPolicy(
+ @ZenPolicy.ConversationSenders int zpConversationSenders,
+ @Policy.ConversationSenders int defaultResult) {
+ switch (zpConversationSenders) {
+ case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
+ return Policy.CONVERSATION_SENDERS_ANYONE;
+ case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
+ return Policy.CONVERSATION_SENDERS_IMPORTANT;
+ case ZenPolicy.CONVERSATION_SENDERS_NONE:
+ return Policy.CONVERSATION_SENDERS_NONE;
+ default:
+ return defaultResult;
+ }
+ }
+
+ /** Maps {@link Policy.ConversationSenders} enum to {@link ZenPolicy.ConversationSenders}. */
+ @ZenPolicy.ConversationSenders
+ private static int notificationPolicyConversationSendersToZenPolicy(
+ @Policy.ConversationSenders int npPriorityConversationSenders) {
+ switch (npPriorityConversationSenders) {
+ case Policy.CONVERSATION_SENDERS_ANYONE:
+ return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+ case Policy.CONVERSATION_SENDERS_IMPORTANT:
+ return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ case Policy.CONVERSATION_SENDERS_NONE:
+ return ZenPolicy.CONVERSATION_SENDERS_NONE;
+ default: // including Policy.CONVERSATION_SENDERS_UNSET
+ return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+ }
+ }
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d9ca935..1d6dd1e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -24,6 +24,9 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
+import static android.service.notification.ZenAdapters.notificationPolicySendersToZenPolicyPeopleType;
+import static android.service.notification.ZenAdapters.zenPolicyConversationSendersToNotificationPolicy;
+import static android.service.notification.ZenAdapters.zenPolicyPeopleTypeToNotificationPolicySenders;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -1269,11 +1272,11 @@
public ZenPolicy toZenPolicy() {
ZenPolicy.Builder builder = new ZenPolicy.Builder()
.allowCalls(allowCalls
- ? ZenModeConfig.getZenPolicySenders(allowCallsFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowCallsFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowRepeatCallers(allowRepeatCallers)
.allowMessages(allowMessages
- ? ZenModeConfig.getZenPolicySenders(allowMessagesFrom)
+ ? notificationPolicySendersToZenPolicyPeopleType(allowMessagesFrom)
: ZenPolicy.PEOPLE_TYPE_NONE)
.allowReminders(allowReminders)
.allowEvents(allowEvents)
@@ -1333,14 +1336,14 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_MESSAGES,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_MESSAGES, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_MESSAGES;
- messageSenders = getNotificationPolicySenders(zenPolicy.getPriorityMessageSenders(),
- messageSenders);
+ messageSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityMessageSenders(), messageSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CONVERSATIONS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CONVERSATIONS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
- conversationSenders = getConversationSendersWithDefault(
+ conversationSenders = zenPolicyConversationSendersToNotificationPolicy(
zenPolicy.getPriorityConversationSenders(), conversationSenders);
} else {
conversationSenders = CONVERSATION_SENDERS_NONE;
@@ -1349,8 +1352,8 @@
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
isPriorityCategoryEnabled(Policy.PRIORITY_CATEGORY_CALLS, defaultPolicy))) {
priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS;
- callSenders = getNotificationPolicySenders(zenPolicy.getPriorityCallSenders(),
- callSenders);
+ callSenders = zenPolicyPeopleTypeToNotificationPolicySenders(
+ zenPolicy.getPriorityCallSenders(), callSenders);
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_REPEAT_CALLERS,
@@ -1449,47 +1452,6 @@
return (policy.suppressedVisualEffects & visualEffect) == 0;
}
- private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.PEOPLE_TYPE_ANYONE:
- return Policy.PRIORITY_SENDERS_ANY;
- case ZenPolicy.PEOPLE_TYPE_CONTACTS:
- return Policy.PRIORITY_SENDERS_CONTACTS;
- case ZenPolicy.PEOPLE_TYPE_STARRED:
- return Policy.PRIORITY_SENDERS_STARRED;
- default:
- return defaultPolicySender;
- }
- }
-
- private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
- int defaultPolicySender) {
- switch (senders) {
- case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
- case ZenPolicy.CONVERSATION_SENDERS_IMPORTANT:
- case ZenPolicy.CONVERSATION_SENDERS_NONE:
- return senders;
- default:
- return defaultPolicySender;
- }
- }
-
- /**
- * Maps NotificationManager.Policy senders type to ZenPolicy.PeopleType
- */
- public static @ZenPolicy.PeopleType int getZenPolicySenders(int senders) {
- switch (senders) {
- case Policy.PRIORITY_SENDERS_ANY:
- return ZenPolicy.PEOPLE_TYPE_ANYONE;
- case Policy.PRIORITY_SENDERS_CONTACTS:
- return ZenPolicy.PEOPLE_TYPE_CONTACTS;
- case Policy.PRIORITY_SENDERS_STARRED:
- default:
- return ZenPolicy.PEOPLE_TYPE_STARRED;
- }
- }
-
public Policy toNotificationPolicy() {
int priorityCategories = 0;
int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS;
@@ -1524,7 +1486,7 @@
}
priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders);
priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders);
- priorityConversationSenders = getConversationSendersWithDefault(
+ priorityConversationSenders = zenPolicyConversationSendersToNotificationPolicy(
allowConversationsFrom, priorityConversationSenders);
int state = areChannelsBypassingDnd ? Policy.STATE_CHANNELS_BYPASSING_DND : 0;
@@ -1559,15 +1521,6 @@
}
}
- private static int prioritySendersToSource(int prioritySenders, int def) {
- switch (prioritySenders) {
- case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT;
- case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR;
- case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE;
- default: return def;
- }
- }
-
private static int normalizePrioritySenders(int prioritySenders, int def) {
if (!(prioritySenders == Policy.PRIORITY_SENDERS_CONTACTS
|| prioritySenders == Policy.PRIORITY_SENDERS_STARRED
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
index e44c69c..6dbff71 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl
@@ -36,11 +36,13 @@
*/
oneway interface IOnDeviceIntelligenceService {
void getVersion(in RemoteCallback remoteCallback);
- void getFeature(in int featureId, in IFeatureCallback featureCallback);
- void listFeatures(in IListFeaturesCallback listFeaturesCallback);
- void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
+ void getFeature(int callerUid, int featureId, in IFeatureCallback featureCallback);
+ void listFeatures(int callerUid, in IListFeaturesCallback listFeaturesCallback);
+ void getFeatureDetails(int callerUid, in Feature feature, in IFeatureDetailsCallback featureDetailsCallback);
void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future);
void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback);
- void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
+ void requestFeatureDownload(int callerUid, in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback);
void registerRemoteServices(in IRemoteProcessingService remoteProcessingService);
+ void notifyInferenceServiceConnected();
+ void notifyInferenceServiceDisconnected();
}
\ No newline at end of file
diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
similarity index 74%
rename from core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
rename to core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
index e3fda04..73257ed 100644
--- a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl
+++ b/core/java/android/service/ondeviceintelligence/IOnDeviceSandboxedInferenceService.aidl
@@ -18,7 +18,7 @@
import android.app.ondeviceintelligence.IStreamingResponseCallback;
import android.app.ondeviceintelligence.IResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.Content;
import android.app.ondeviceintelligence.Feature;
@@ -29,18 +29,18 @@
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
/**
- * Interface for a concrete implementation to provide on device trusted inference.
+ * Interface for a concrete implementation to provide on-device sandboxed inference.
*
* @hide
*/
-oneway interface IOnDeviceTrustedInferenceService {
+oneway interface IOnDeviceSandboxedInferenceService {
void registerRemoteStorageService(in IRemoteStorageService storageService);
- void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
- in ITokenCountCallback tokenCountCallback);
- void processRequest(in Feature feature, in Content request, in int requestType,
+ void requestTokenInfo(int callerUid, in Feature feature, in Content request, in ICancellationSignal cancellationSignal,
+ in ITokenInfoCallback tokenInfoCallback);
+ void processRequest(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IResponseCallback callback);
- void processRequestStreaming(in Feature feature, in Content request, in int requestType,
+ void processRequestStreaming(int callerUid, in Feature feature, in Content request, in int requestType,
in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal,
in IStreamingResponseCallback callback);
void updateProcessingState(in Bundle processingState,
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
index 46ba25d..fce3689 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java
@@ -65,7 +65,7 @@
/**
* Abstract base class for performing setup for on-device inference and providing file access to
- * the isolated counter part {@link OnDeviceTrustedInferenceService}.
+ * the isolated counter part {@link OnDeviceSandboxedInferenceService}.
*
* <p> A service that provides configuration and model files relevant to performing inference on
* device. The system's default OnDeviceIntelligenceService implementation is configured in
@@ -110,6 +110,8 @@
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ // TODO(326052028) : Move the remote method calls to an app handler from the binder
+ // thread.
return new IOnDeviceIntelligenceService.Stub() {
/** {@inheritDoc} */
@Override
@@ -123,38 +125,40 @@
}
@Override
- public void listFeatures(IListFeaturesCallback listFeaturesCallback) {
+ public void listFeatures(int callerUid,
+ IListFeaturesCallback listFeaturesCallback) {
Objects.requireNonNull(listFeaturesCallback);
- OnDeviceIntelligenceService.this.onListFeatures(
+ OnDeviceIntelligenceService.this.onListFeatures(callerUid,
wrapListFeaturesCallback(listFeaturesCallback));
}
@Override
- public void getFeature(int id, IFeatureCallback featureCallback) {
+ public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
Objects.requireNonNull(featureCallback);
- OnDeviceIntelligenceService.this.onGetFeature(id,
- wrapFeatureCallback(featureCallback));
+ OnDeviceIntelligenceService.this.onGetFeature(callerUid,
+ id, wrapFeatureCallback(featureCallback));
}
@Override
- public void getFeatureDetails(Feature feature,
+ public void getFeatureDetails(int callerUid, Feature feature,
IFeatureDetailsCallback featureDetailsCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(featureDetailsCallback);
- OnDeviceIntelligenceService.this.onGetFeatureDetails(feature,
- wrapFeatureDetailsCallback(featureDetailsCallback));
+ OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid,
+ feature, wrapFeatureDetailsCallback(featureDetailsCallback));
}
@Override
- public void requestFeatureDownload(Feature feature,
+ public void requestFeatureDownload(int callerUid, Feature feature,
ICancellationSignal cancellationSignal,
IDownloadCallback downloadCallback) {
Objects.requireNonNull(feature);
Objects.requireNonNull(downloadCallback);
- OnDeviceIntelligenceService.this.onDownloadFeature(feature,
+ OnDeviceIntelligenceService.this.onDownloadFeature(callerUid,
+ feature,
CancellationSignal.fromTransport(cancellationSignal),
wrapDownloadCallback(downloadCallback));
}
@@ -188,12 +192,38 @@
IRemoteProcessingService remoteProcessingService) {
mRemoteProcessingService = remoteProcessingService;
}
+
+ @Override
+ public void notifyInferenceServiceConnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceConnected();
+ }
+
+ @Override
+ public void notifyInferenceServiceDisconnected() {
+ OnDeviceIntelligenceService.this.onInferenceServiceDisconnected();
+ }
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
return null;
}
+
+ /**
+ * Invoked when a new instance of the remote inference service is created.
+ * This method should be used as a signal to perform any initialization operations, for e.g. by
+ * invoking the {@link #updateProcessingState} method to initialize the remote processing
+ * service.
+ */
+ public abstract void onInferenceServiceConnected();
+
+
+ /**
+ * Invoked when an instance of the remote inference service is disconnected.
+ */
+ public abstract void onInferenceServiceDisconnected();
+
+
/**
* Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
* service if there is a state change to be performed.
@@ -391,6 +421,7 @@
* Request download for feature that is requested and listen to download progress updates. If
* the download completes successfully, success callback should be populated.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature the feature for which files need to be downlaoded.
* process.
* @param cancellationSignal signal to attach a listener to, and receive cancellation signals
@@ -398,7 +429,7 @@
* @param downloadCallback callback to populate download updates for clients to listen on..
*/
public abstract void onDownloadFeature(
- @NonNull Feature feature,
+ int callerUid, @NonNull Feature feature,
@Nullable CancellationSignal cancellationSignal,
@NonNull DownloadCallback downloadCallback);
@@ -407,20 +438,22 @@
* implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
* details the client is looking for.
*
- * @param feature the feature for which status needs to be known.
- * @param featureStatusCallback callback to populate the resulting feature status.
+ * @param callerUid UID of the caller that initiated this call chain.
+ * @param feature the feature for which status needs to be known.
+ * @param featureDetailsCallback callback to populate the resulting feature status.
*/
- public abstract void onGetFeatureDetails(@NonNull Feature feature,
+ public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
@NonNull OutcomeReceiver<FeatureDetails,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback);
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureDetailsCallback);
/**
* Get feature using the provided identifier to the remote implementation.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param featureCallback callback to populate the features list.
*/
- public abstract void onGetFeature(int featureId,
+ public abstract void onGetFeature(int callerUid, int featureId,
@NonNull OutcomeReceiver<Feature,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureCallback);
@@ -428,9 +461,10 @@
* List all features which are available in the remote implementation. The implementation might
* choose to provide only a certain list of features based on the caller.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param listFeaturesCallback callback to populate the features list.
*/
- public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>,
+ public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> listFeaturesCallback);
/**
diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
similarity index 73%
rename from core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
rename to core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
index 8600197..7f7f9c2 100644
--- a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java
+++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@@ -16,6 +16,7 @@
package android.service.ondeviceintelligence;
+import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
import android.annotation.CallbackExecutor;
@@ -23,6 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.Service;
import android.app.ondeviceintelligence.Content;
@@ -30,14 +32,18 @@
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.app.ondeviceintelligence.ProcessingSignal;
-import android.app.ondeviceintelligence.StreamingResponseReceiver;
+import android.app.ondeviceintelligence.ProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.StreamedProcessingOutcomeReceiver;
+import android.app.ondeviceintelligence.TokenInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -75,8 +81,8 @@
*
* <pre>
* {@literal
- * <service android:name=".SampleTrustedInferenceService"
- * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE"
+ * <service android:name=".SampleSandboxedInferenceService"
+ * android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
* android:isolatedProcess="true">
* </service>}
* </pre>
@@ -85,18 +91,18 @@
*/
@SystemApi
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
-public abstract class OnDeviceTrustedInferenceService extends Service {
- private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName();
+public abstract class OnDeviceSandboxedInferenceService extends Service {
+ private static final String TAG = OnDeviceSandboxedInferenceService.class.getSimpleName();
/**
* The {@link Intent} that must be declared as handled by the service. To be supported, the
* service must also require the
- * {@link android.Manifest.permission#BIND_ON_DEVICE_TRUSTED_INFERENCE_SERVICE}
+ * {@link android.Manifest.permission#BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE}
* permission so that other applications can not abuse it.
*/
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String SERVICE_INTERFACE =
- "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService";
+ "android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
private IRemoteStorageService mRemoteStorageService;
@@ -107,7 +113,7 @@
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
- return new IOnDeviceTrustedInferenceService.Stub() {
+ return new IOnDeviceSandboxedInferenceService.Stub() {
@Override
public void registerRemoteStorageService(IRemoteStorageService storageService) {
Objects.requireNonNull(storageService);
@@ -115,50 +121,48 @@
}
@Override
- public void requestTokenCount(Feature feature, Content request,
+ public void requestTokenInfo(int callerUid, Feature feature, Content request,
ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountCallback) {
+ ITokenInfoCallback tokenInfoCallback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(tokenCountCallback);
- OnDeviceTrustedInferenceService.this.onCountTokens(feature,
+ Objects.requireNonNull(tokenInfoCallback);
+ OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid,
+ feature,
request,
CancellationSignal.fromTransport(cancellationSignal),
- wrapTokenCountCallback(tokenCountCallback));
+ wrapTokenInfoCallback(tokenInfoCallback));
}
@Override
- public void processRequestStreaming(Feature feature, Content request,
+ public void processRequestStreaming(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IStreamingResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature,
+ OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid,
+ feature,
request,
requestType,
CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapStreamingResponseCallback(callback)
- );
+ wrapStreamingResponseCallback(callback));
}
@Override
- public void processRequest(Feature feature, Content request,
+ public void processRequest(int callerUid, Feature feature, Content request,
int requestType, ICancellationSignal cancellationSignal,
IProcessingSignal processingSignal,
IResponseCallback callback) {
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(callback);
-
- OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request,
- requestType, CancellationSignal.fromTransport(cancellationSignal),
+ OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature,
+ request, requestType,
+ CancellationSignal.fromTransport(cancellationSignal),
ProcessingSignal.fromTransport(processingSignal),
- wrapResponseCallback(callback)
- );
+ wrapResponseCallback(callback));
}
@Override
@@ -167,7 +171,7 @@
Objects.requireNonNull(processingState);
Objects.requireNonNull(callback);
- OnDeviceTrustedInferenceService.this.onUpdateProcessingState(processingState,
+ OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState,
wrapOutcomeReceiver(callback)
);
}
@@ -178,35 +182,37 @@
}
/**
- * Invoked when caller wants to obtain a count of number of tokens present in the passed in
- * Request associated with the provided feature.
+ * Invoked when caller wants to obtain token info related to the payload in the passed
+ * content, associated with the provided feature.
* The expectation from the implementation is that when processing is complete, it
- * should provide the token count in the {@link OutcomeReceiver#onResult}.
+ * should provide the token info in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param cancellationSignal Cancellation Signal to receive cancellation events from client and
* configure a listener to.
- * @param callback callback to populate failure and full response for the provided
+ * @param callback callback to populate failure or the token info for the provided
* request.
*/
@NonNull
- public abstract void onCountTokens(
- @NonNull Feature feature,
+ public abstract void onTokenInfoRequest(
+ int callerUid, @NonNull Feature feature,
@NonNull Content request,
@Nullable CancellationSignal cancellationSignal,
- @NonNull OutcomeReceiver<Long,
+ @NonNull OutcomeReceiver<TokenInfo,
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in a
* streaming manner. The expectation from the implementation is that when processing the
* request,
- * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously
+ * it periodically populates the {@link StreamedProcessingOutcomeReceiver#onNewContent} to continuously
* provide partial Content results for the caller to utilize. Optionally the implementation can
- * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon
+ * provide the complete response in the {@link StreamedProcessingOutcomeReceiver#onResult} upon
* processing completion.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -218,13 +224,12 @@
*/
@NonNull
public abstract void onProcessRequestStreaming(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull StreamedProcessingOutcomeReceiver callback);
/**
* Invoked when caller provides a request for a particular feature to be processed in one shot
@@ -233,6 +238,7 @@
* should
* provide the complete response in the {@link OutcomeReceiver#onResult}.
*
+ * @param callerUid UID of the caller that initiated this call chain.
* @param feature feature which is associated with the request.
* @param request request that requires processing.
* @param requestType identifier representing the type of request.
@@ -244,13 +250,12 @@
*/
@NonNull
public abstract void onProcessRequest(
- @NonNull Feature feature,
- @NonNull Content request,
+ int callerUid, @NonNull Feature feature,
+ @Nullable Content request,
@OnDeviceIntelligenceManager.RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
- @NonNull OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback);
+ @NonNull ProcessingOutcomeReceiver callback);
/**
@@ -335,6 +340,26 @@
}
}
+
+ /**
+ * Returns the {@link Executor} to use for incoming IPC from request sender into your service
+ * implementation. For e.g. see
+ * {@link ProcessingOutcomeReceiver#onDataAugmentRequest(Content,
+ * Consumer)} where we use the executor to populate the consumer.
+ * <p>
+ * Override this method in your {@link OnDeviceSandboxedInferenceService} implementation to
+ * provide the executor you want to use for incoming IPC.
+ *
+ * @return the {@link Executor} to use for incoming IPC from {@link OnDeviceIntelligenceManager}
+ * to {@link OnDeviceSandboxedInferenceService}.
+ */
+ @SuppressLint("OnNameExpected")
+ @NonNull
+ public Executor getCallbackExecutor() {
+ return new HandlerExecutor(Handler.createAsync(getMainLooper()));
+ }
+
+
private RemoteCallback wrapResultReceiverAsReadOnly(
@NonNull Consumer<Map<String, FileInputStream>> resultConsumer,
@NonNull Executor executor) {
@@ -355,10 +380,9 @@
});
}
- private OutcomeReceiver<Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback(
+ private ProcessingOutcomeReceiver wrapResponseCallback(
IResponseCallback callback) {
- return new OutcomeReceiver<>() {
+ return new ProcessingOutcomeReceiver() {
@Override
public void onResult(@androidx.annotation.NonNull Content response) {
try {
@@ -378,13 +402,23 @@
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private StreamingResponseReceiver<Content, Content,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback(
+ private StreamedProcessingOutcomeReceiver wrapStreamingResponseCallback(
IStreamingResponseCallback callback) {
- return new StreamingResponseReceiver<>() {
+ return new StreamedProcessingOutcomeReceiver() {
@Override
public void onNewContent(@androidx.annotation.NonNull Content content) {
try {
@@ -413,17 +447,43 @@
Slog.e(TAG, "Error sending result: " + e);
}
}
+
+ @Override
+ public void onDataAugmentRequest(@NonNull Content content,
+ @NonNull Consumer<Content> contentCallback) {
+ try {
+ callback.onDataAugmentRequest(content, wrapRemoteCallback(contentCallback));
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending augment request: " + e);
+ }
+ }
};
}
- private OutcomeReceiver<Long,
- OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback(
- ITokenCountCallback tokenCountCallback) {
+ private RemoteCallback wrapRemoteCallback(
+ @androidx.annotation.NonNull Consumer<Content> contentCallback) {
+ return new RemoteCallback(
+ result -> {
+ if (result != null) {
+ getCallbackExecutor().execute(() -> contentCallback.accept(
+ result.getParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY,
+ Content.class)));
+ } else {
+ getCallbackExecutor().execute(
+ () -> contentCallback.accept(null));
+ }
+ });
+ }
+
+ private OutcomeReceiver<TokenInfo,
+ OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenInfoCallback(
+ ITokenInfoCallback tokenInfoCallback) {
return new OutcomeReceiver<>() {
@Override
- public void onResult(Long tokenCount) {
+ public void onResult(TokenInfo tokenInfo) {
try {
- tokenCountCallback.onSuccess(tokenCount);
+ tokenInfoCallback.onSuccess(tokenInfo);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending result: " + e);
}
@@ -433,7 +493,7 @@
public void onError(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) {
try {
- tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
+ tokenInfoCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
exception.getErrorParams());
} catch (RemoteException e) {
Slog.e(TAG, "Error sending failure: " + e);
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index 68e8c72..0ae3e59 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1733,6 +1733,8 @@
private static native long nativeCopy(long destNativePtr, long sourceNativePtr,
boolean keepHistory);
@CriticalNative
+ private static native long nativeSplit(long destNativePtr, long sourceNativePtr, int idBits);
+ @CriticalNative
private static native int nativeGetId(long nativePtr);
@CriticalNative
private static native int nativeGetDeviceId(long nativePtr);
@@ -1773,9 +1775,9 @@
@CriticalNative
private static native void nativeOffsetLocation(long nativePtr, float deltaX, float deltaY);
@CriticalNative
- private static native float nativeGetXOffset(long nativePtr);
+ private static native float nativeGetRawXOffset(long nativePtr);
@CriticalNative
- private static native float nativeGetYOffset(long nativePtr);
+ private static native float nativeGetRawYOffset(long nativePtr);
@CriticalNative
private static native float nativeGetXPrecision(long nativePtr);
@CriticalNative
@@ -3743,7 +3745,7 @@
nativeGetAction(mNativePtr), nativeGetFlags(mNativePtr),
nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
+ nativeGetRawXOffset(mNativePtr), nativeGetRawYOffset(mNativePtr),
nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
nativeGetDownTimeNanos(mNativePtr),
nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT),
@@ -3767,86 +3769,23 @@
}
/**
- * Splits a motion event such that it includes only a subset of pointer ids.
+ * Splits a motion event such that it includes only a subset of pointer IDs.
+ * @param idBits the bitset indicating all of the pointer IDs from this motion event that should
+ * be in the new split event. idBits must be a non-empty subset of the pointer IDs
+ * contained in this event.
* @hide
*/
+ // TODO(b/327503168): Pass downTime as a parameter to split.
@UnsupportedAppUsage
+ @NonNull
public final MotionEvent split(int idBits) {
- MotionEvent ev = obtain();
- synchronized (gSharedTempLock) {
- final int oldPointerCount = nativeGetPointerCount(mNativePtr);
- ensureSharedTempPointerCapacity(oldPointerCount);
- final PointerProperties[] pp = gSharedTempPointerProperties;
- final PointerCoords[] pc = gSharedTempPointerCoords;
- final int[] map = gSharedTempPointerIndexMap;
-
- final int oldAction = nativeGetAction(mNativePtr);
- final int oldActionMasked = oldAction & ACTION_MASK;
- final int oldActionPointerIndex = (oldAction & ACTION_POINTER_INDEX_MASK)
- >> ACTION_POINTER_INDEX_SHIFT;
- int newActionPointerIndex = -1;
- int newPointerCount = 0;
- for (int i = 0; i < oldPointerCount; i++) {
- nativeGetPointerProperties(mNativePtr, i, pp[newPointerCount]);
- final int idBit = 1 << pp[newPointerCount].id;
- if ((idBit & idBits) != 0) {
- if (i == oldActionPointerIndex) {
- newActionPointerIndex = newPointerCount;
- }
- map[newPointerCount] = i;
- newPointerCount += 1;
- }
- }
-
- if (newPointerCount == 0) {
- throw new IllegalArgumentException("idBits did not match any ids in the event");
- }
-
- final int newAction;
- if (oldActionMasked == ACTION_POINTER_DOWN || oldActionMasked == ACTION_POINTER_UP) {
- if (newActionPointerIndex < 0) {
- // An unrelated pointer changed.
- newAction = ACTION_MOVE;
- } else if (newPointerCount == 1) {
- // The first/last pointer went down/up.
- newAction = oldActionMasked == ACTION_POINTER_DOWN
- ? ACTION_DOWN
- : (getFlags() & FLAG_CANCELED) == 0 ? ACTION_UP : ACTION_CANCEL;
- } else {
- // A secondary pointer went down/up.
- newAction = oldActionMasked
- | (newActionPointerIndex << ACTION_POINTER_INDEX_SHIFT);
- }
- } else {
- // Simple up/down/cancel/move or other motion action.
- newAction = oldAction;
- }
-
- final int historySize = nativeGetHistorySize(mNativePtr);
- for (int h = 0; h <= historySize; h++) {
- final int historyPos = h == historySize ? HISTORY_CURRENT : h;
-
- for (int i = 0; i < newPointerCount; i++) {
- nativeGetPointerCoords(mNativePtr, map[i], historyPos, pc[i]);
- }
-
- final long eventTimeNanos = nativeGetEventTimeNanos(mNativePtr, historyPos);
- if (h == 0) {
- ev.initialize(nativeGetDeviceId(mNativePtr), nativeGetSource(mNativePtr),
- nativeGetDisplayId(mNativePtr),
- newAction, nativeGetFlags(mNativePtr),
- nativeGetEdgeFlags(mNativePtr), nativeGetMetaState(mNativePtr),
- nativeGetButtonState(mNativePtr), nativeGetClassification(mNativePtr),
- nativeGetXOffset(mNativePtr), nativeGetYOffset(mNativePtr),
- nativeGetXPrecision(mNativePtr), nativeGetYPrecision(mNativePtr),
- nativeGetDownTimeNanos(mNativePtr), eventTimeNanos,
- newPointerCount, pp, pc);
- } else {
- nativeAddBatch(ev.mNativePtr, eventTimeNanos, pc, 0);
- }
- }
- return ev;
+ if (idBits == 0) {
+ throw new IllegalArgumentException(
+ "idBits must contain at least one pointer from this motion event");
}
+ MotionEvent event = obtain();
+ event.mNativePtr = nativeSplit(event.mNativePtr, this.mNativePtr, idBits);
+ return event;
}
/**
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
index 8a44d4f..798203f 100644
--- a/core/java/android/view/ScrollFeedbackProvider.java
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -21,8 +21,11 @@
import android.view.flags.Flags;
/**
- * Interface to represent an entity giving consistent feedback for different events surrounding view
- * scroll.
+ * Provides feedback to the user for scroll events on a {@link View}. The type of feedback provided
+ * to the user may depend on the {@link InputDevice} that generated the scroll events.
+ *
+ * <p>An example of the type of feedback that this interface may provide is haptic feedback (that
+ * is, tactile feedback that provide the user physical feedback for their scroll).
*
* <p>The interface provides methods for the client to report different scroll events. The client
* should report all scroll events that they want to be considered for scroll feedback using the
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 124aece..c66abe8 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -889,11 +889,11 @@
* @hide
*/
@Override
- protected int calculateFrameRateCategory(float sizePercentage) {
+ protected int calculateFrameRateCategory(int width, int height) {
if (mMinusTwoFrameIntervalMillis > 15 && mMinusOneFrameIntervalMillis > 15) {
return FRAME_RATE_CATEGORY_NORMAL;
}
- return super.calculateFrameRateCategory(sizePercentage);
+ return super.calculateFrameRateCategory(width, height);
}
@UnsupportedAppUsage
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 828004b..f0bde97 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -25,6 +25,7 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
@@ -2370,6 +2371,39 @@
*/
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
+ /**
+ * This indicates that the frame rate category was chosen because it was a small area update.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_SMALL = 0x0100_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was an intermittent update.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_INTERMITTENT = 0x0200_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was a large View.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_LARGE = 0x03000000;
+
+ /**
+ * This indicates that the frame rate category was chosen because it was requested.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_REQUESTED = 0x0400_0000;
+
+ /**
+ * This indicates that the frame rate category was chosen because an invalid frame rate was
+ * requested.
+ * @hide
+ */
+ public static final int FRAME_RATE_CATEGORY_REASON_INVALID = 0x0500_0000;
+
+ private static final int FRAME_RATE_CATEGORY_REASON_MASK = 0xFFFF_0000;
+
private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
// Used to set frame rate compatibility.
@@ -5629,17 +5663,24 @@
@Nullable
private ViewTranslationCallback mViewTranslationCallback;
- private float mFrameContentVelocity = 0;
+ private float mFrameContentVelocity = -1;
@Nullable
private ViewTranslationResponse mViewTranslationResponse;
/**
- * A threshold value to determine the frame rate category of the View based on the size.
+ * Threshold size for something to be considered a small area update (in DP).
+ * This is the dimension for both width and height.
*/
- private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+ private static final float FRAME_RATE_SMALL_SIZE_THRESHOLD = 40f;
+ /**
+ * Threshold size for something to be considered a small area update (in DP) if
+ * it is narrow. This is for either width OR height. For example, a narrow progress
+ * bar could be considered a small area.
+ */
+ private static final float FRAME_RATE_NARROW_THRESHOLD = 10f;
private static final long INFREQUENT_UPDATE_INTERVAL_MILLIS = 100;
private static final int INFREQUENT_UPDATE_COUNTS = 2;
@@ -5660,6 +5701,9 @@
protected long mMinusTwoFrameIntervalMillis = 0;
private int mLastFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+ private float mLastFrameX = Float.NaN;
+ private float mLastFrameY = Float.NaN;
+
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = Float.NaN;
@FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
@@ -24597,7 +24641,10 @@
public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
- mFrameContentVelocity = 0;
+
+ mFrameContentVelocity = -1;
+ mLastFrameX = mLeft + mRenderNode.getTranslationX();
+ mLastFrameY = mTop + mRenderNode.getTranslationY();
/*
* Draw traversal performs several drawing steps which must be executed
@@ -33648,18 +33695,28 @@
*
* @hide
*/
- protected int calculateFrameRateCategory(float sizePercentage) {
+ protected int calculateFrameRateCategory(int width, int height) {
if (mMinusTwoFrameIntervalMillis + mMinusOneFrameIntervalMillis
< INFREQUENT_UPDATE_INTERVAL_MILLIS) {
- if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
- return FRAME_RATE_CATEGORY_NORMAL;
+ DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+ float density = displayMetrics.density;
+ if (density == 0f) {
+ density = 1f;
+ }
+ float widthDp = width / density;
+ float heightDp = height / density;
+ if (widthDp <= FRAME_RATE_NARROW_THRESHOLD
+ || heightDp <= FRAME_RATE_NARROW_THRESHOLD
+ || (widthDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD
+ && heightDp <= FRAME_RATE_SMALL_SIZE_THRESHOLD)) {
+ return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_SMALL;
} else {
- return FRAME_RATE_CATEGORY_HIGH;
+ return FRAME_RATE_CATEGORY_HIGH | FRAME_RATE_CATEGORY_REASON_LARGE;
}
}
if (mInfrequentUpdateCount == INFREQUENT_UPDATE_COUNTS) {
- return FRAME_RATE_CATEGORY_NORMAL;
+ return FRAME_RATE_CATEGORY_NORMAL | FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
}
return mLastFrameRateCategory;
}
@@ -33667,34 +33724,74 @@
private void votePreferredFrameRate() {
// use toolkitSetFrameRate flag to gate the change
ViewRootImpl viewRootImpl = getViewRootImpl();
- float sizePercentage = getSizePercentage();
- int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
- if (viewRootImpl != null && sizePercentage > 0) {
- if (sToolkitMetricsForFrameRateDecisionFlagValue) {
- viewRootImpl.recordViewPercentage(sizePercentage);
- }
- if (!Float.isNaN(mPreferredFrameRate)) {
- if (mPreferredFrameRate < 0) {
- if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
- frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
- frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
- } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
- frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
- }
- } else {
- viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
- mFrameRateCompatibility);
+ int width = mRight - mLeft;
+ int height = mBottom - mTop;
+ if (viewRootImpl != null && (width != 0 && height != 0)) {
+ if (viewVelocityApi()) {
+ float velocity = mFrameContentVelocity;
+ if (velocity < 0f) {
+ velocity = calculateVelocity();
+ }
+ if (velocity > 0f) {
+ float frameRate = convertVelocityToFrameRate(velocity);
+ viewRootImpl.votePreferredFrameRate(frameRate, FRAME_RATE_COMPATIBILITY_GTE);
return;
}
}
- viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
- mLastFrameRateCategory = frameRateCateogry;
+ int frameRateCategory;
+ if (Float.isNaN(mPreferredFrameRate)) {
+ frameRateCategory = calculateFrameRateCategory(width, height);
+ } else if (mPreferredFrameRate < 0) {
+ if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+ frameRateCategory = FRAME_RATE_CATEGORY_LOW
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+ frameRateCategory = FRAME_RATE_CATEGORY_NORMAL
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_REQUESTED;
+ } else {
+ // invalid frame rate, default to HIGH
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH
+ | FRAME_RATE_CATEGORY_REASON_INVALID;
+ }
+ } else {
+ viewRootImpl.votePreferredFrameRate(mPreferredFrameRate,
+ mFrameRateCompatibility);
+ return;
+ }
+
+ int category = frameRateCategory & ~FRAME_RATE_CATEGORY_REASON_MASK;
+ if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+ int reason = frameRateCategory & FRAME_RATE_CATEGORY_REASON_MASK;
+ viewRootImpl.recordCategory(category, reason, this);
+ }
+ viewRootImpl.votePreferredFrameRateCategory(category);
+ mLastFrameRateCategory = frameRateCategory;
}
}
+ private float convertVelocityToFrameRate(float velocityPps) {
+ float density = getResources().getDisplayMetrics().density;
+ float velocityDps = velocityPps / density;
+ // Choose a frame rate in increments of 10fps
+ return Math.min(140f, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+ }
+
+ private float calculateVelocity() {
+ // This current calculation is very simple. If something on the screen moved, then
+ // it votes for the highest velocity. If it doesn't move, then return 0.
+ float x = mLeft + mRenderNode.getTranslationX();
+ float y = mTop + mRenderNode.getTranslationY();
+
+ return (!Float.isNaN(mLastFrameX) && (x != mLastFrameX || y != mLastFrameY))
+ ? 100_000f : 0f;
+ }
+
/**
* Set the current velocity of the View, we only track positive value.
* We will use the velocity information to adjust the frame rate when applicable.
@@ -33725,7 +33822,7 @@
@FlaggedApi(FLAG_VIEW_VELOCITY_API)
public float getFrameContentVelocity() {
if (viewVelocityApi()) {
- return mFrameContentVelocity;
+ return (mFrameContentVelocity < 0f) ? 0f : mFrameContentVelocity;
}
return 0;
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a7cb169..0bc9197 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -31,6 +31,12 @@
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INTERMITTENT;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_INVALID;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_LARGE;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_REQUESTED;
+import static android.view.View.FRAME_RATE_CATEGORY_REASON_SMALL;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -840,8 +846,9 @@
private boolean mInsetsAnimationRunning;
private long mPreviousFrameDrawnTime = -1;
- // The largest view size percentage to the display size. Used on trace to collect metric.
- private float mLargestChildPercentage = 0.0f;
+ // The reason the category was changed.
+ private int mFrameRateCategoryChangeReason = 0;
+ private String mFrameRateCategoryView;
/**
* The resolved pointer icon type requested by this window.
@@ -3447,7 +3454,10 @@
// other windows to resize/move based on the raw frame of the window, waiting until we
// can finish laying out this window and get back to the window manager with the
// ultimately computed insets.
- insetsPending = computesInternalInsets;
+ insetsPending = computesInternalInsets
+ // If this window provides insets via params, its insets source frame can be
+ // updated directly without waiting for WindowSession#setInsets.
+ && mWindowAttributes.providedInsets == null;
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
@@ -4843,10 +4853,6 @@
long fps = NANOS_PER_SEC / timeDiff;
Trace.setCounter(mFpsTraceName, fps);
mPreviousFrameDrawnTime = expectedDrawnTime;
-
- long percentage = (long) (mLargestChildPercentage * 100);
- Trace.setCounter(mLargestViewTraceName, percentage);
- mLargestChildPercentage = 0.0f;
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -7568,7 +7574,8 @@
}
// For the variable refresh rate project
- if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+ if (handled && shouldTouchBoost(action & MotionEvent.ACTION_MASK,
+ mWindowAttributes.type)) {
// set the frame rate to the maximum value.
mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12355,16 +12362,29 @@
// For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
// FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
// (e.g., Window Initialization).
- if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning
+ || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+ && mPreferredFrameRate > 0)) {
frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
}
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ String reason = "none";
+ switch (mFrameRateCategoryChangeReason) {
+ case FRAME_RATE_CATEGORY_REASON_INTERMITTENT -> reason = "intermittent";
+ case FRAME_RATE_CATEGORY_REASON_SMALL -> reason = "small";
+ case FRAME_RATE_CATEGORY_REASON_LARGE -> reason = "large";
+ case FRAME_RATE_CATEGORY_REASON_REQUESTED -> reason = "requested";
+ case FRAME_RATE_CATEGORY_REASON_INVALID -> reason = "invalid frame rate";
+ }
+ String sourceView = mFrameRateCategoryView == null ? "No View Given"
+ : mFrameRateCategoryView;
Trace.traceBegin(
Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRateCategory "
- + frameRateCategory);
+ + frameRateCategory + ", reason " + reason + ", "
+ + sourceView);
}
mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
frameRateCategory, false).applyAsyncUnsafe();
@@ -12378,7 +12398,8 @@
}
private void setPreferredFrameRate(float preferredFrameRate) {
- if (!shouldSetFrameRate()) {
+ if (!shouldSetFrameRate() || (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE
+ && preferredFrameRate > 0)) {
return;
}
@@ -12395,6 +12416,17 @@
mFrameRateCompatibility).applyAsyncUnsafe();
mLastPreferredFrameRate = preferredFrameRate;
}
+ if (mFrameRateCompatibility == FRAME_RATE_COMPATIBILITY_GTE && mIsTouchBoosting) {
+ // We've received a velocity, so we'll let the velocity control the
+ // frame rate unless we receive additional motion events.
+ mIsTouchBoosting = false;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ Trace.instant(
+ Trace.TRACE_TAG_VIEW,
+ "ViewRootImpl#setFrameRate velocity used, no touch boost on next frame"
+ );
+ }
+ }
} catch (Exception e) {
Log.e(mTag, "Unable to set frame rate", e);
} finally {
@@ -12420,9 +12452,8 @@
}
private boolean shouldTouchBoost(int motionEventAction, int windowType) {
- boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
- || motionEventAction == MotionEvent.ACTION_MOVE
- || motionEventAction == MotionEvent.ACTION_UP;
+ // boost for almost all input
+ boolean desiredAction = motionEventAction != MotionEvent.ACTION_OUTSIDE;
boolean undesiredType = windowType == TYPE_INPUT_METHOD
&& sToolkitFrameRateTypingReadOnlyFlagValue;
// use toolkitSetFrameRate flag to gate the change
@@ -12528,6 +12559,22 @@
}
/**
+ * Get the value of mLastPreferredFrameRate
+ */
+ @VisibleForTesting
+ public float getLastPreferredFrameRate() {
+ return mLastPreferredFrameRate;
+ }
+
+ /**
+ * Returns whether touch boost is currently enabled.
+ */
+ @VisibleForTesting
+ public boolean getIsTouchBoosting() {
+ return mIsTouchBoosting;
+ }
+
+ /**
* Get the value of mFrameRateCompatibility
*/
@VisibleForTesting
@@ -12569,10 +12616,12 @@
mWindowlessBackKeyCallback = callback;
}
- void recordViewPercentage(float percentage) {
+ void recordCategory(int category, int reason, View view) {
if (!Trace.isEnabled()) return;
- // Record the largest view of percentage to the display size.
- mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+ if (category > mPreferredFrameRateCategory) {
+ mFrameRateCategoryChangeReason = reason;
+ mFrameRateCategoryView = view.getClass().getSimpleName();
+ }
}
/**
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 131fca7..1efd375 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -315,7 +315,8 @@
/**
* Add to this view's child count. This increases the current child count by
* <var>num</var> children beyond what was last set by {@link #setChildCount}
- * or {@link #addChildCount}. The index at which the new child starts in the child
+ * or {@link #addChildCount}. The index at which the new
+ * child starts in the child
* array is returned.
*
* @param num The number of new children to add.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 64e5a5b..cf6b9e5 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1160,12 +1160,10 @@
// denylist only applies to not important views
if (!view.isImportantForAutofill() && 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;
}
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 7f79661..d992feb 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -182,7 +182,6 @@
PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT,
- PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
PHASE_IME_SHOW_WINDOW,
PHASE_IME_HIDE_WINDOW,
PHASE_IME_PRIVILEGED_OPERATIONS,
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 16fecc1..8ddc178 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -499,6 +499,25 @@
@TestApi
public InputMethodInfo(@NonNull String packageName, @NonNull String className,
@NonNull CharSequence label, @NonNull String settingsActivity,
+ boolean supportStylusHandwriting,
+ @NonNull String stylusHandwritingSettingsActivityAttr) {
+ this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
+ settingsActivity, null /* languageSettingsActivity */,
+ null /* subtypes */, 0 /* isDefaultResId */,
+ false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */,
+ false /* inlineSuggestionsEnabled */, false /* isVrOnly */,
+ false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */,
+ supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */,
+ stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */);
+ }
+
+ /**
+ * Test API for creating a built-in input method to verify stylus handwriting.
+ * @hide
+ */
+ @TestApi
+ public InputMethodInfo(@NonNull String packageName, @NonNull String className,
+ @NonNull CharSequence label, @NonNull String settingsActivity,
@NonNull String languageSettingsActivity, boolean supportStylusHandwriting,
@NonNull String stylusHandwritingSettingsActivityAttr) {
this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */,
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3be76cc..985f542 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1153,7 +1153,6 @@
}
final boolean startInput;
synchronized (mH) {
- mImeDispatcher.clear();
if (getBindSequenceLocked() != sequence) {
return;
}
@@ -2909,8 +2908,6 @@
* @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
* @param executor The executor to run the callback on.
* @param callback {@code true>} would be received if delegation was accepted.
- * @return {@code true} if view belongs to allowed delegate package declared in {@link
- * #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
* @see #prepareStylusHandwritingDelegation(View, String)
* @see #acceptStylusHandwritingDelegation(View)
*/
diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java
index 3fc0a30..8501474 100644
--- a/core/java/android/webkit/WebViewDelegate.java
+++ b/core/java/android/webkit/WebViewDelegate.java
@@ -175,8 +175,16 @@
/**
* Adds the WebView asset path to {@link android.content.res.AssetManager}.
+ * If {@link android.content.res.Flags#FLAG_REGISTER_RESOURCE_PATHS} is enabled, this function
+ * will be a no-op because the asset paths appending work will only be handled by
+ * {@link android.content.res.Resources#registerResourcePaths(String, ApplicationInfo)},
+ * otherwise it behaves the old way.
*/
public void addWebViewAssetPath(Context context) {
+ if (android.content.res.Flags.registerResourcePaths()) {
+ return;
+ }
+
final String[] newAssetPaths =
WebViewFactory.getLoadedPackageInfo().applicationInfo.getAllApkPaths();
final ApplicationInfo appInfo = context.getApplicationInfo();
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index c748a57..8f1b72e 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -30,6 +30,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
+import android.content.res.Resources;
import android.os.Build;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -93,6 +94,9 @@
// error for namespace lookup
public static final int LIBLOAD_FAILED_TO_FIND_NAMESPACE = 10;
+ // generic error for future use
+ static final int LIBLOAD_FAILED_OTHER = 11;
+
/**
* Stores the timestamps at which various WebView startup events occurred in this process.
*/
@@ -544,8 +548,14 @@
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "WebViewFactory.getChromiumProviderClass()");
try {
sTimestamps.mAddAssetsStart = SystemClock.uptimeMillis();
- for (String newAssetPath : webViewContext.getApplicationInfo().getAllApkPaths()) {
- initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ if (android.content.res.Flags.registerResourcePaths()) {
+ Resources.registerResourcePaths(webViewContext.getPackageName(),
+ webViewContext.getApplicationInfo());
+ } else {
+ for (String newAssetPath : webViewContext.getApplicationInfo()
+ .getAllApkPaths()) {
+ initialApplication.getAssets().addAssetPathAsSharedLibrary(newAssetPath);
+ }
}
sTimestamps.mAddAssetsEnd = sTimestamps.mGetClassLoaderStart =
SystemClock.uptimeMillis();
diff --git a/core/java/android/webkit/WebViewProviderResponse.java b/core/java/android/webkit/WebViewProviderResponse.java
index 84e34a3..926aaff 100644
--- a/core/java/android/webkit/WebViewProviderResponse.java
+++ b/core/java/android/webkit/WebViewProviderResponse.java
@@ -40,6 +40,7 @@
STATUS_SUCCESS,
STATUS_FAILED_WAITING_FOR_RELRO,
STATUS_FAILED_LISTING_WEBVIEW_PACKAGES,
+ STATUS_FAILED_OTHER,
})
@Retention(RetentionPolicy.SOURCE)
private @interface WebViewProviderStatus {}
@@ -49,6 +50,7 @@
WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO;
public static final int STATUS_FAILED_LISTING_WEBVIEW_PACKAGES =
WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES;
+ public static final int STATUS_FAILED_OTHER = WebViewFactory.LIBLOAD_FAILED_OTHER;
public WebViewProviderResponse(
@Nullable PackageInfo packageInfo, @WebViewProviderStatus int status) {
diff --git a/core/java/android/webkit/WebViewUpdateManager.java b/core/java/android/webkit/WebViewUpdateManager.java
index 8ada598..07576a2 100644
--- a/core/java/android/webkit/WebViewUpdateManager.java
+++ b/core/java/android/webkit/WebViewUpdateManager.java
@@ -127,7 +127,7 @@
*
* This choice will be stored persistently.
*
- * @param newProvider the package name to use, or null to reset to default.
+ * @param newProvider the package name to use.
* @return the package name which is now in use, which may not be the
* requested one if it was not usable.
*/
@@ -155,7 +155,7 @@
/**
* Get the WebView provider which will be used if no explicit choice has been made.
*
- * The default provider is not guaranteed to be currently valid/usable.
+ * The default provider is not guaranteed to be a valid/usable WebView implementation.
*
* @return the default WebView provider.
*/
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 55b2251..0b99df3 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -1488,6 +1490,11 @@
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
}
}
@@ -1810,6 +1817,11 @@
mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0,
maxScroll, 0, 0, width / 2, 0);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
final boolean movingRight = velocityX > 0;
View currentFocused = findFocus();
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0e5747d..a2d8d80 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1105,6 +1105,7 @@
SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
mViewId = parcel.readInt();
mIntentId = parcel.readInt();
+ mIsReplacedIntoAction = parcel.readBoolean();
mServiceIntent = parcel.readTypedObject(Intent.CREATOR);
mItems = mServiceIntent != null
? null
@@ -1128,6 +1129,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mViewId);
dest.writeInt(mIntentId);
+ dest.writeBoolean(mIsReplacedIntoAction);
dest.writeTypedObject(mServiceIntent, flags);
if (mItems != null) {
mItems.writeToParcel(dest, flags, /* attached= */ true);
@@ -1209,6 +1211,19 @@
}
/**
+ * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter.
+ * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size
+ * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit
+ * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer
+ * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the
+ * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews
+ * directly to the parcel as we did in RemoteViewsService)
+ *
+ * @hide
+ */
+ private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8);
+
+ /**
* @hide
*/
public CompletableFuture<Void> collectAllIntents() {
@@ -1260,17 +1275,47 @@
return mUriToCollectionMapping.get(uri);
}
- CompletableFuture<Void> collectAllIntentsNoComplete(@NonNull RemoteViews inViews) {
- CompletableFuture<Void> collectionFuture = CompletableFuture.completedFuture(null);
+ public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete(
+ @NonNull RemoteViews inViews) {
+ SparseArray<Intent> idToIntentMapping = new SparseArray<>();
+ // Collect the number of uinque Intent (which is equal to the number of new connections
+ // to make) for size allocation and exclude certain collections from being written to
+ // the parcel to better estimate the space left for reallocation.
+ collectAllIntentsInternal(inViews, idToIntentMapping);
+
+ // Calculate the individual size here
+ int numOfIntents = idToIntentMapping.size();
+ if (numOfIntents == 0) {
+ Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id");
+ return CompletableFuture.completedFuture(null);
+ }
+
+ Parcel sizeTestParcel = Parcel.obtain();
+ // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection
+ // cache to see how much space is left for the RemoteCollectionItems that are to be
+ // updated.
+ RemoteViews.this.writeToParcel(sizeTestParcel,
+ /* flags= */ 0,
+ /* intentsToIgnore= */ idToIntentMapping);
+ int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize();
+ sizeTestParcel.recycle();
+
+ int individualSize = remainingSize < 0
+ ? 0
+ : remainingSize / numOfIntents;
+
+ return connectAllUniqueIntents(individualSize, idToIntentMapping);
+ }
+
+ private void collectAllIntentsInternal(@NonNull RemoteViews inViews,
+ @NonNull SparseArray<Intent> idToIntentMapping) {
if (inViews.hasSizedRemoteViews()) {
for (RemoteViews remoteViews : inViews.mSizedRemoteViews) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(remoteViews));
+ collectAllIntentsInternal(remoteViews, idToIntentMapping);
}
} else if (inViews.hasLandscapeAndPortraitLayouts()) {
- collectionFuture = CompletableFuture.allOf(
- collectAllIntentsNoComplete(inViews.mLandscape),
- collectAllIntentsNoComplete(inViews.mPortrait));
+ collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping);
+ collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping);
} else if (inViews.mActions != null) {
for (Action action : inViews.mActions) {
if (action instanceof SetRemoteCollectionItemListAdapterAction rca) {
@@ -1280,13 +1325,16 @@
}
if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) {
- final String uri = mIdToUriMapping.get(rca.mIntentId);
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
- .thenAccept(rc -> {
- rc.setHierarchyRootData(getHierarchyRootData());
- mUriToCollectionMapping.put(uri, rc);
- }));
+ rca.mIsReplacedIntoAction = false;
+
+ // Avoid redundant connections for the same intent. Also making sure
+ // that the number of connections we are making is always equal to the
+ // nmuber of unique intents that are being used for the updates.
+ if (idToIntentMapping.contains(rca.mIntentId)) {
+ continue;
+ }
+
+ idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
rca.mItems = null;
continue;
}
@@ -1295,7 +1343,7 @@
// intents.
if (rca.mServiceIntent != null) {
final String uri = rca.mServiceIntent.toUri(0);
- int index = mIdToUriMapping.indexOfValue(uri);
+ int index = mIdToUriMapping.indexOfValueByValue(uri);
if (index == -1) {
int newIntentId = mIdToUriMapping.size();
rca.mIntentId = newIntentId;
@@ -1305,41 +1353,50 @@
rca.mItems = null;
continue;
}
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- getItemsFutureFromIntentWithTimeout(rca.mServiceIntent)
- .thenAccept(rc -> {
- rc.setHierarchyRootData(getHierarchyRootData());
- mUriToCollectionMapping.put(uri, rc);
- }));
+
+ idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent);
rca.mItems = null;
} else {
for (RemoteViews views : rca.mItems.mViews) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(views));
+ collectAllIntentsInternal(views, idToIntentMapping);
}
}
} else if (action instanceof ViewGroupActionAdd vgaa
&& vgaa.mNestedViews != null) {
- collectionFuture = CompletableFuture.allOf(collectionFuture,
- collectAllIntentsNoComplete(vgaa.mNestedViews));
+ collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping);
}
}
}
+ }
- return collectionFuture;
+ private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize,
+ @NonNull SparseArray<Intent> idToIntentMapping) {
+ List<CompletableFuture<Void>> intentFutureList = new ArrayList<>();
+ for (int i = 0; i < idToIntentMapping.size(); i++) {
+ String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i));
+ Intent currentIntent = idToIntentMapping.valueAt(i);
+ intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent,
+ individualSize)
+ .thenAccept(items -> {
+ items.setHierarchyRootData(getHierarchyRootData());
+ mUriToCollectionMapping.put(currentIntentUri, items);
+ }));
+ }
+
+ return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new));
}
private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout(
- Intent intent) {
+ Intent intent, int individualSize) {
if (intent == null) {
Log.e(LOG_TAG, "Null intent received when generating adapter future");
return CompletableFuture.completedFuture(new RemoteCollectionItems
- .Builder().build());
+ .Builder().build());
}
final Context context = ActivityThread.currentApplication();
- final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
+ final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>();
context.bindService(intent, Context.BindServiceFlags.of(Context.BIND_AUTO_CREATE),
result.defaultExecutor(), new ServiceConnection() {
@Override
@@ -1348,11 +1405,11 @@
RemoteCollectionItems items;
try {
items = IRemoteViewsFactory.Stub.asInterface(iBinder)
- .getRemoteCollectionItems();
+ .getRemoteCollectionItems(individualSize);
} catch (RemoteException re) {
items = new RemoteCollectionItems.Builder().build();
- Log.e(LOG_TAG, "Error getting collection items from the factory",
- re);
+ Log.e(LOG_TAG, "Error getting collection items from the"
+ + " factory", re);
} finally {
context.unbindService(this);
}
@@ -1371,10 +1428,17 @@
return result;
}
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(Parcel out, int flags,
+ @Nullable SparseArray<Intent> intentsToIgnore) {
out.writeInt(mIdToUriMapping.size());
for (int i = 0; i < mIdToUriMapping.size(); i++) {
- out.writeInt(mIdToUriMapping.keyAt(i));
+ int currentIntentId = mIdToUriMapping.keyAt(i);
+ if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) {
+ // Skip writing collections that are to be updated in the following steps to
+ // better estimate the RemoteViews size.
+ continue;
+ }
+ out.writeInt(currentIntentId);
String intentUri = mIdToUriMapping.valueAt(i);
out.writeString8(intentUri);
mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
@@ -4235,7 +4299,7 @@
}
if (mode == MODE_NORMAL) {
- mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel);
+ mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR);
mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
mLayoutId = parcel.readInt();
mViewId = parcel.readInt();
@@ -6724,7 +6788,13 @@
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
+ writeToParcel(dest, flags, /* intentsToIgnore= */ null);
+ }
+
+ private void writeToParcel(Parcel dest, int flags,
+ @Nullable SparseArray<Intent> intentsToIgnore) {
boolean prevSquashingAllowed = dest.allowSquashing();
if (!hasMultipleLayouts()) {
@@ -6733,9 +6803,9 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
- mApplication.writeToParcel(dest, flags);
+ dest.writeTypedObject(mApplication, flags);
if (mIsRoot || mIdealSize == null) {
dest.writeInt(0);
} else {
@@ -6750,7 +6820,7 @@
dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
dest.writeInt(mSizedRemoteViews.size());
for (RemoteViews view : mSizedRemoteViews) {
@@ -6762,7 +6832,7 @@
// is shared by all children.
if (mIsRoot) {
mBitmapCache.writeBitmapsToParcel(dest, flags);
- mCollectionCache.writeToParcel(dest, flags);
+ mCollectionCache.writeToParcel(dest, flags, intentsToIgnore);
}
mLandscape.writeToParcel(dest, flags);
// Both RemoteViews already share the same package and user
@@ -6823,7 +6893,8 @@
* @hide
*/
public boolean hasSameAppInfo(ApplicationInfo info) {
- return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
+ return mApplication == null || mApplication.packageName.equals(info.packageName)
+ && mApplication.uid == info.uid;
}
/**
@@ -7602,8 +7673,7 @@
byte[] instruction;
final List<byte[]> instructions = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
- instruction = new byte[in.readInt()];
- in.readByteArray(instruction);
+ instruction = in.readBlob();
instructions.add(instruction);
}
return new DrawInstructions(instructions);
@@ -7618,8 +7688,7 @@
final List<byte[]> instructions = drawInstructions.mInstructions;
dest.writeInt(instructions.size());
for (byte[] instruction : instructions) {
- dest.writeInt(instruction.length);
- dest.writeByteArray(instruction);
+ dest.writeBlob(instruction);
}
}
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index a250a86..d4a5bbd 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -19,6 +19,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.os.Parcel;
import com.android.internal.widget.IRemoteViewsFactory;
@@ -43,13 +44,6 @@
private static final Object sLock = new Object();
/**
- * Used for determining the maximum number of entries to retrieve from RemoteViewsFactory
- *
- * @hide
- */
- private static final int MAX_NUM_ENTRY = 10;
-
- /**
* An interface for an adapter between a remote collection view (ListView, GridView, etc) and
* the underlying data for that view. The implementor is responsible for making a RemoteView
* for each item in the data set. This interface is a thin wrapper around {@link Adapter}.
@@ -235,9 +229,10 @@
}
@Override
- public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems
.Builder().build();
+ Parcel capSizeTestParcel = Parcel.obtain();
try {
RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
@@ -245,15 +240,25 @@
mFactory.onDataSetChanged();
itemsBuilder.setHasStableIds(mFactory.hasStableIds());
- final int numOfEntries = Math.min(mFactory.getCount(), MAX_NUM_ENTRY);
+ final int numOfEntries = mFactory.getCount();
+
for (int i = 0; i < numOfEntries; i++) {
- itemsBuilder.addItem(mFactory.getItemId(i), mFactory.getViewAt(i));
+ final long currentItemId = mFactory.getItemId(i);
+ final RemoteViews currentView = mFactory.getViewAt(i);
+ currentView.writeToParcel(capSizeTestParcel, 0);
+ if (capSizeTestParcel.dataSize() > capSize) {
+ break;
+ }
+ itemsBuilder.addItem(currentItemId, currentView);
}
items = itemsBuilder.build();
} catch (Exception ex) {
Thread t = Thread.currentThread();
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, ex);
+ } finally {
+ // Recycle the parcel
+ capSizeTestParcel.recycle();
}
return items;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a1ebde7..42c2d80 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.flags.Flags.viewVelocityApi;
+
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
@@ -726,6 +728,12 @@
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
+
mIsBeingDragged = !mScroller.isFinished() || !mEdgeGlowBottom.isFinished()
|| !mEdgeGlowTop.isFinished();
// Catch the edge effect if it is active.
@@ -1573,6 +1581,11 @@
// Keep on drawing until the animation has finished.
postInvalidateOnAnimation();
}
+
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
} else {
if (mFlingStrictSpan != null) {
mFlingStrictSpan.finish();
@@ -1884,6 +1897,10 @@
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math.max(0, bottom - height), 0, height/2);
+ // For variable refresh rate project to track the current velocity of this View
+ if (viewVelocityApi()) {
+ setFrameContentVelocity(Math.abs(mScroller.getCurrVelocity()));
+ }
if (mFlingStrictSpan == null) {
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 65b5979..c769518 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -172,6 +172,20 @@
newIntent.prepareToLeaveUser(callingUserId);
final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+
+ if (isPrivateProfile(callingUserId)) {
+ buildAndExecuteForPrivateProfile(intentReceived, className, newIntent, callingUserId,
+ targetUserId);
+ } else {
+ buildAndExecute(targetResolveInfoFuture, intentReceived, className, newIntent,
+ callingUserId,
+ targetUserId, userMessage, managedProfile);
+ }
+ }
+
+ private void buildAndExecute(CompletableFuture<ResolveInfo> targetResolveInfoFuture,
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId, String userMessage, UserInfo managedProfile) {
targetResolveInfoFuture
.thenApplyAsync(targetResolveInfo -> {
if (isResolverActivityResolveInfo(targetResolveInfo)) {
@@ -195,6 +209,23 @@
}, getApplicationContext().getMainExecutor());
}
+ private void buildAndExecuteForPrivateProfile(
+ Intent intentReceived, String className, Intent newIntent, int callingUserId,
+ int targetUserId) {
+ final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+ mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+ targetResolveInfoFuture
+ .thenAcceptAsync(targetResolveInfo -> {
+ if (isResolverActivityResolveInfo(targetResolveInfo)) {
+ launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+ callingUserId, targetUserId);
+ } else {
+ maybeShowUserConsentMiniResolverPrivate(targetResolveInfo, newIntent,
+ targetUserId);
+ }
+ }, getApplicationContext().getMainExecutor());
+ }
+
private void maybeShowUserConsentMiniResolver(
ResolveInfo target, Intent launchIntent, UserInfo managedProfile) {
if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) {
@@ -233,24 +264,70 @@
"Showing user consent for redirection into the managed profile for intent [%s] and "
+ " calling package [%s]",
launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+
+ // Additional information section is work telephony specific. Therefore, it is only shown
+ // for telephony related intents, when all sim subscriptions are in the work profile.
+ if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
+ && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
+ == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
+ telephonyInfo.setVisibility(View.VISIBLE);
+ ((TextView) findViewById(R.id.miniresolver_info_section_text))
+ .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
+ } else {
+ telephonyInfo.setVisibility(View.GONE);
+ }
+ }
+
+ private void maybeShowUserConsentMiniResolverPrivate(
+ ResolveInfo target, Intent launchIntent, int targetUserId) {
+ if (target == null || isIntentForwarderResolveInfo(target)) {
+ finish();
+ return;
+ }
+
+ String callingPackage = getCallingPackage();
+
+ Log.i("IntentForwarderActivity", String.format(
+ "Showing user consent for redirection into the main profile for intent [%s] and "
+ + " calling package [%s]",
+ launchIntent, callingPackage));
+ PackageManager packageManagerForTargetUser =
+ createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
+ .getPackageManager();
+ buildMiniResolver(target, launchIntent, targetUserId,
+ getString(R.string.miniresolver_open_in_personal,
+ target.loadLabel(packageManagerForTargetUser)),
+ packageManagerForTargetUser);
+
+ View telephonyInfo = findViewById(R.id.miniresolver_info_section);
+ telephonyInfo.setVisibility(View.GONE);
+ }
+
+ private void buildMiniResolver(ResolveInfo target, Intent launchIntent, int targetUserId,
+ String resolverTitle, PackageManager pmForTargetUser) {
int layoutId = R.layout.miniresolver;
setContentView(layoutId);
findViewById(R.id.title_container).setElevation(0);
- PackageManager packageManagerForTargetUser =
- createContextAsUser(UserHandle.of(targetUserId), /* flags= */ 0)
- .getPackageManager();
-
ImageView icon = findViewById(R.id.icon);
icon.setImageDrawable(
- getAppIcon(target, launchIntent, targetUserId, packageManagerForTargetUser));
+ getAppIcon(target, launchIntent, targetUserId, pmForTargetUser));
View buttonContainer = findViewById(R.id.button_bar_container);
buttonContainer.setPadding(0, 0, 0, buttonContainer.getPaddingBottom());
((TextView) findViewById(R.id.open_cross_profile)).setText(
- getOpenInWorkMessage(launchIntent, target.loadLabel(packageManagerForTargetUser)));
+ resolverTitle);
// The mini-resolver's negative button is reused in this flow to cancel the intent
((Button) findViewById(R.id.use_same_profile_browser)).setText(R.string.cancel);
@@ -269,21 +346,6 @@
targetUserId);
finish();
});
-
-
- View telephonyInfo = findViewById(R.id.miniresolver_info_section);
-
- // Additional information section is work telephony specific. Therefore, it is only shown
- // for telephony related intents, when all sim subscriptions are in the work profile.
- if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent))
- && devicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType()
- == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
- telephonyInfo.setVisibility(View.VISIBLE);
- ((TextView) findViewById(R.id.miniresolver_info_section_text))
- .setText(getWorkTelephonyInfoSectionMessage(launchIntent));
- } else {
- telephonyInfo.setVisibility(View.GONE);
- }
}
private Drawable getAppIcon(
@@ -548,6 +610,18 @@
}
/**
+ * Returns the private profile for this device or null if there is no private profile.
+ */
+ @Nullable
+ private UserInfo getPrivateProfile() {
+ List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId());
+ for (UserInfo userInfo : relatedUsers) {
+ if (userInfo.isPrivateProfile()) return userInfo;
+ }
+ return null;
+ }
+
+ /**
* Returns the userId of the profile parent or UserHandle.USER_NULL if there is
* no parent.
*/
@@ -577,6 +651,17 @@
return mMetricsLogger;
}
+ private boolean isPrivateProfile(int userId) {
+ UserInfo privateProfile = getPrivateProfile();
+ return privateSpaceFlagsEnabled() && privateProfile != null
+ && privateProfile.id == userId;
+ }
+
+ private boolean privateSpaceFlagsEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceIntentRedirection();
+ }
+
@VisibleForTesting
protected Injector createInjector() {
return new InjectorImpl();
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 7dcbbea..78f06b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2655,7 +2655,8 @@
private boolean privateSpaceEnabled() {
return mIsIntentPicker && android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.allowResolverSheetForPrivateSpace();
+ && android.multiuser.Flags.allowResolverSheetForPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
diff --git a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
index 93fe37c..360fcaf 100644
--- a/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
+++ b/core/java/com/android/internal/app/SetScreenLockDialogActivity.java
@@ -75,7 +75,8 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!(android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.showSetScreenLockDialog())) {
+ && android.multiuser.Flags.showSetScreenLockDialog()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures())) {
finish();
return;
}
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 4ef0a1b..97f8084 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -77,6 +77,7 @@
}
if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& !userManager.isManagedProfile(mUserId)) {
Log.e(TAG, "Unlaunchable activity for target package " + targetPackageName
+ " called for a non-managed-profile " + mUserId);
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index 85bdbb9..e55cdef 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -80,11 +80,10 @@
in Bundle extras, in IntentSender resultIntent);
boolean isRequestPinAppWidgetSupported();
oneway void noteAppWidgetTapped(in String callingPackage, in int appWidgetId);
- void setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
+ boolean setWidgetPreview(in ComponentName providerComponent, in int widgetCategories,
in RemoteViews preview);
@nullable RemoteViews getWidgetPreview(in String callingPackage,
in ComponentName providerComponent, in int profileId, in int widgetCategory);
void removeWidgetPreview(in ComponentName providerComponent, in int widgetCategories);
-
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index bd806bf..91678c7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -560,6 +560,19 @@
*/
public static final String CURSOR_HOVER_STATES_ENABLED = "cursor_hover_states_enabled";
+
+ /*
+ * (long) The reset interval for generated preview API calls.
+ */
+ public static final String GENERATED_PREVIEW_API_RESET_INTERVAL_MS =
+ "generated_preview_api_reset_interval_ms";
+
+ /*
+ * (int) The max number of generated preview API calls per reset interval.
+ */
+ public static final String GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL =
+ "generated_preview_api_max_calls_per_interval";
+
private SystemUiDeviceConfigFlags() {
}
}
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 30de546..2096ba4 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -55,37 +55,38 @@
* A service for the ProtoLog logging system.
*/
public class LegacyProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
-
private static final int BUFFER_CAPACITY = 1024 * 1024;
private static final int PER_CHUNK_SIZE = 1024;
private static final String TAG = "ProtoLog";
private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
static final String PROTOLOG_VERSION = "2.0.0";
- private static final int DEFAULT_PER_CHUNK_SIZE = 0;
private final File mLogFile;
private final String mLegacyViewerConfigFilename;
private final TraceBuffer mBuffer;
private final LegacyProtoLogViewerConfigReader mViewerConfig;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
private final int mPerChunkSize;
private boolean mProtoLogEnabled;
private boolean mProtoLogEnabledLockFree;
private final Object mProtoLogEnabledLock = new Object();
- public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename) {
+ public LegacyProtoLogImpl(String outputFile, String viewerConfigFilename,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(new File(outputFile), viewerConfigFilename, BUFFER_CAPACITY,
- new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE);
+ new LegacyProtoLogViewerConfigReader(), PER_CHUNK_SIZE, logGroups);
}
public LegacyProtoLogImpl(File file, String viewerConfigFilename, int bufferCapacity,
- LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize) {
+ LegacyProtoLogViewerConfigReader viewerConfig, int perChunkSize,
+ TreeMap<String, IProtoLogGroup> logGroups) {
mLogFile = file;
mBuffer = new TraceBuffer(bufferCapacity);
mLegacyViewerConfigFilename = viewerConfigFilename;
mViewerConfig = viewerConfig;
mPerChunkSize = perChunkSize;
+ this.mLogGroups = logGroups;
}
/**
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 53062d8..4ead82f 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -22,9 +22,9 @@
import static perfetto.protos.PerfettoTrace.InternedString.STR;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.BOOLEAN_PARAMS;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.DOUBLE_PARAMS;
-import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.MESSAGE_ID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.SINT64_PARAMS;
+import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STACKTRACE_IID;
import static perfetto.protos.PerfettoTrace.ProtoLogMessage.STR_PARAM_IIDS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.GROUPS;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.Group.ID;
@@ -70,6 +70,8 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData;
@@ -78,7 +80,6 @@
* A service for the ProtoLog logging system.
*/
public class PerfettoProtoLogImpl implements IProtoLog {
- private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
private static final String LOG_TAG = "ProtoLog";
private final AtomicInteger mTracingInstances = new AtomicInteger();
@@ -89,8 +90,12 @@
);
private final ProtoLogViewerConfigReader mViewerConfigReader;
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ private final TreeMap<String, IProtoLogGroup> mLogGroups;
- public PerfettoProtoLogImpl(String viewerConfigFilePath) {
+ private final ExecutorService mBackgroundLoggingService = Executors.newCachedThreadPool();
+
+ public PerfettoProtoLogImpl(String viewerConfigFilePath,
+ TreeMap<String, IProtoLogGroup> logGroups) {
this(() -> {
try {
return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
@@ -98,23 +103,28 @@
Slog.w(LOG_TAG, "Failed to load viewer config file " + viewerConfigFilePath, e);
return null;
}
- });
+ }, logGroups);
}
- public PerfettoProtoLogImpl(ViewerConfigInputStreamProvider viewerConfigInputStreamProvider) {
+ public PerfettoProtoLogImpl(
+ ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ TreeMap<String, IProtoLogGroup> logGroups
+ ) {
this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
+ new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider), logGroups);
}
@VisibleForTesting
public PerfettoProtoLogImpl(
ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- ProtoLogViewerConfigReader viewerConfigReader
+ ProtoLogViewerConfigReader viewerConfigReader,
+ TreeMap<String, IProtoLogGroup> logGroups
) {
Producer.init(InitArguments.DEFAULTS);
mDataSource.register(DataSourceParams.DEFAULTS);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
+ this.mLogGroups = logGroups;
}
/**
@@ -128,7 +138,8 @@
long tsNanos = SystemClock.elapsedRealtimeNanos();
try {
- logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos);
+ mBackgroundLoggingService.submit(() ->
+ logToProto(level, group.name(), messageHash, paramsMask, args, tsNanos));
if (group.isLogToLogcat()) {
logToLogcat(group.getTag(), level, messageHash, messageString, args);
}
@@ -456,40 +467,6 @@
}
/**
- * Responds to a shell command.
- */
- public int onShellCommand(ShellCommand shell) {
- PrintWriter pw = shell.getOutPrintWriter();
- String cmd = shell.getNextArg();
- if (cmd == null) {
- return unknownCommand(pw);
- }
- ArrayList<String> args = new ArrayList<>();
- String arg;
- while ((arg = shell.getNextArg()) != null) {
- args.add(arg);
- }
- final ILogger logger = (msg) -> logAndPrintln(pw, msg);
- String[] groups = args.toArray(new String[args.size()]);
- switch (cmd) {
- case "enable-text":
- return this.startLoggingToLogcat(groups, logger);
- case "disable-text":
- return this.stopLoggingToLogcat(groups, logger);
- default:
- return unknownCommand(pw);
- }
- }
-
- private int unknownCommand(PrintWriter pw) {
- pw.println("Unknown command");
- pw.println("Window manager logging options:");
- pw.println(" enable-text [group...]: Enable logcat logging for given groups");
- pw.println(" disable-text [group...]: Disable logcat logging for given groups");
- return -1;
- }
-
- /**
* Returns {@code true} iff logging to proto is enabled.
*/
public boolean isProtoEnabled() {
@@ -548,6 +525,49 @@
return 0;
}
+ /**
+ * Responds to a shell command.
+ */
+ public int onShellCommand(ShellCommand shell) {
+ PrintWriter pw = shell.getOutPrintWriter();
+ String cmd = shell.getNextArg();
+ if (cmd == null) {
+ return unknownCommand(pw);
+ }
+ ArrayList<String> args = new ArrayList<>();
+ String arg;
+ while ((arg = shell.getNextArg()) != null) {
+ args.add(arg);
+ }
+ final ILogger logger = (msg) -> logAndPrintln(pw, msg);
+ String[] groups = args.toArray(new String[0]);
+ switch (cmd) {
+ case "start", "stop" -> {
+ pw.println("Command not supported. "
+ + "Please start and stop ProtoLog tracing with Perfetto.");
+ return -1;
+ }
+ case "enable-text" -> {
+ mViewerConfigReader.loadViewerConfig(logger);
+ return setTextLogging(true, logger, groups);
+ }
+ case "disable-text" -> {
+ return setTextLogging(false, logger, groups);
+ }
+ default -> {
+ return unknownCommand(pw);
+ }
+ }
+ }
+
+ private int unknownCommand(PrintWriter pw) {
+ pw.println("Unknown command");
+ pw.println("Window manager logging options:");
+ pw.println(" enable-text [group...]: Enable logcat logging for given groups");
+ pw.println(" disable-text [group...]: Disable logcat logging for given groups");
+ return -1;
+ }
+
static void logAndPrintln(@Nullable PrintWriter pw, String msg) {
Slog.i(LOG_TAG, msg);
if (pw != null) {
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 8965385..487ae814 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -18,6 +18,7 @@
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_OUTPUT_FILE_PATH;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LEGACY_VIEWER_CONFIG_PATH;
+import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LOG_GROUPS;
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
@@ -28,6 +29,8 @@
import com.android.internal.protolog.common.LogLevel;
import com.android.internal.protolog.common.ProtoLogToolInjected;
+import java.util.TreeMap;
+
/**
* A service for the ProtoLog logging system.
*/
@@ -43,6 +46,9 @@
@ProtoLogToolInjected(LEGACY_OUTPUT_FILE_PATH)
private static String sLegacyOutputFilePath;
+ @ProtoLogToolInjected(LOG_GROUPS)
+ private static TreeMap<String, IProtoLogGroup> sLogGroups;
+
/** Used by the ProtoLogTool, do not call directly - use {@code ProtoLog} class instead. */
public static void d(IProtoLogGroup group, long messageHash, int paramsMask,
@Nullable String messageString,
@@ -99,11 +105,10 @@
public static synchronized IProtoLog getSingleInstance() {
if (sServiceInstance == null) {
if (android.tracing.Flags.perfettoProtologTracing()) {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath);
+ sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sLogGroups);
} else {
- sServiceInstance =
- new LegacyProtoLogImpl(sLegacyOutputFilePath, sLegacyViewerConfigPath);
+ sServiceInstance = new LegacyProtoLogImpl(
+ sLegacyOutputFilePath, sLegacyViewerConfigPath, sLogGroups);
}
}
return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
index 3c206ac..ae3d448 100644
--- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
+++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java
@@ -4,6 +4,7 @@
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE;
import static perfetto.protos.PerfettoTrace.ProtoLogViewerConfig.MessageData.MESSAGE_ID;
+import android.util.ArrayMap;
import android.util.proto.ProtoInputStream;
import com.android.internal.protolog.common.ILogger;
@@ -57,6 +58,7 @@
}
private void doLoadViewerConfig() throws IOException {
+ mLogMessageMap = new ArrayMap<>();
final ProtoInputStream pis = mViewerConfigInputStreamProvider.getInputStream();
while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
diff --git a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
index ffd0d76..17c82d7 100644
--- a/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
+++ b/core/java/com/android/internal/protolog/common/ProtoLogToolInjected.java
@@ -22,7 +22,9 @@
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface ProtoLogToolInjected {
- enum Value { VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH }
+ enum Value {
+ VIEWER_CONFIG_PATH, LEGACY_OUTPUT_FILE_PATH, LEGACY_VIEWER_CONFIG_PATH, LOG_GROUPS
+ }
Value value();
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
index 918d9c0..1d0e972 100644
--- a/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+++ b/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
@@ -39,6 +39,6 @@
boolean hasStableIds();
@UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
boolean isCreated();
- RemoteViews.RemoteCollectionItems getRemoteCollectionItems();
+ RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize);
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index b7cb392..7c9fda5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -92,7 +92,7 @@
mIndex = 0;
mStartingIndex = 0;
mSize = 0;
- if (expectedSize > mMaxSize) {
+ if (expectedSize >= mMaxSize) {
resize(expectedSize);
}
}
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index d5f17da..83b6afa 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -196,14 +196,6 @@
return result;
}
-static vector<string> parseCsv(JNIEnv* env, jstring csvJString) {
- const char* charArray = env->GetStringUTFChars(csvJString, 0);
- string csvString(charArray);
- vector<string> result = parseCsv(csvString);
- env->ReleaseStringUTFChars(csvJString, charArray);
- return result;
-}
-
void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
unsigned int line, const char* message) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
@@ -395,20 +387,28 @@
jmethodID getPropertyMethod = GetStaticMethodIDOrDie(env, system, "getProperty",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
- // Get the names of classes that need to register their native methods
- auto nativesClassesJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("core_native_classes"),
- env->NewStringUTF(""));
- vector<string> classesToRegister = parseCsv(env, nativesClassesJString);
+ // Java system properties that contain LayoutLib config. The initial values in the map
+ // are the default values if the property is not specified.
+ std::unordered_map<std::string, std::string> systemProperties =
+ {{"core_native_classes", ""},
+ {"register_properties_during_load", ""},
+ {"icu.data.path", ""},
+ {"use_bridge_for_logging", ""},
+ {"keyboard_paths", ""}};
- jstring registerProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF(
- "register_properties_during_load"),
- env->NewStringUTF(""));
- const char* registerPropertyString = env->GetStringUTFChars(registerProperty, 0);
- if (strcmp(registerPropertyString, "true") == 0) {
+ for (auto& [name, defaultValue] : systemProperties) {
+ jstring propertyString =
+ (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
+ env->NewStringUTF(name.c_str()),
+ env->NewStringUTF(defaultValue.c_str()));
+ const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
+ systemProperties[name] = string(propertyChars);
+ env->ReleaseStringUTFChars(propertyString, propertyChars);
+ }
+ // Get the names of classes that need to register their native methods
+ vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);
+
+ if (systemProperties["register_properties_during_load"] == "true") {
// Set the system properties first as they could be used in the static initialization of
// other classes
if (register_android_os_SystemProperties(env) < 0) {
@@ -423,35 +423,20 @@
env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
property_initialize_ro_cpu_abilist();
}
- env->ReleaseStringUTFChars(registerProperty, registerPropertyString);
if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
return JNI_ERR;
}
- // Set the location of ICU data
- auto stringPath = (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("icu.data.path"),
- env->NewStringUTF(""));
- const char* path = env->GetStringUTFChars(stringPath, 0);
-
- if (strcmp(path, "**n/a**") != 0) {
- bool icuInitialized = init_icu(path);
+ if (!systemProperties["icu.data.path"].empty()) {
+ // Set the location of ICU data
+ bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
if (!icuInitialized) {
- fprintf(stderr, "Failed to initialize ICU\n");
return JNI_ERR;
}
- } else {
- fprintf(stderr, "Skip initializing ICU\n");
}
- env->ReleaseStringUTFChars(stringPath, path);
- jstring useJniProperty =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("use_bridge_for_logging"),
- env->NewStringUTF(""));
- const char* useJniString = env->GetStringUTFChars(useJniProperty, 0);
- if (strcmp(useJniString, "true") == 0) {
+ if (systemProperties["use_bridge_for_logging"] == "true") {
layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
layoutLog = MakeGlobalRefOrDie(env, layoutLog);
logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
@@ -468,23 +453,16 @@
// initialize logging, so ANDROD_LOG_TAGS env variable is respected
android::base::InitLogging(nullptr, android::base::StderrLogger);
}
- env->ReleaseStringUTFChars(useJniProperty, useJniString);
// Use English locale for number format to ensure correct parsing of floats when using strtof
setlocale(LC_NUMERIC, "en_US.UTF-8");
- auto keyboardPathsJString =
- (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
- env->NewStringUTF("keyboard_paths"),
- env->NewStringUTF(""));
- const char* keyboardPathsString = env->GetStringUTFChars(keyboardPathsJString, 0);
- if (strcmp(keyboardPathsString, "**n/a**") != 0) {
- vector<string> keyboardPaths = parseCsv(env, keyboardPathsJString);
+ if (!systemProperties["keyboard_paths"].empty()) {
+ vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
init_keyboard(env, keyboardPaths);
} else {
fprintf(stderr, "Skip initializing keyboard\n");
}
- env->ReleaseStringUTFChars(keyboardPathsJString, keyboardPathsString);
return JNI_VERSION_1_6;
}
diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp
index 16c3ca9..1eff5ce 100644
--- a/core/jni/android_tracing_PerfettoDataSource.cpp
+++ b/core/jni/android_tracing_PerfettoDataSource.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
+#define LOG_TAG "NativeJavaPerfettoDs"
#include "android_tracing_PerfettoDataSource.h"
@@ -166,16 +166,25 @@
void PerfettoDataSource::trace(JNIEnv* env, jobject traceFunction) {
PERFETTO_DS_TRACE(dataSource, ctx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "\tin native trace callback function %p", this);
TlsState* tls_state =
reinterpret_cast<TlsState*>(PerfettoDsGetCustomTls(&dataSource, &ctx));
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(
PerfettoDsGetIncrementalState(&dataSource, &ctx));
+ ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state = %p", tls_state);
+ ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state = %p", incr_state);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "\t tls_state->jobj = %p", tls_state->jobj);
+ ALOG(LOG_DEBUG, LOG_TAG, "\t incr_state->jobj = %p", incr_state->jobj);
+
ScopedLocalRef<jobject> jCtx(env,
env->NewObject(gTracingContextClassInfo.clazz,
gTracingContextClassInfo.init, &ctx,
tls_state->jobj, incr_state->jobj));
+ ALOG(LOG_DEBUG, LOG_TAG, "\t jCtx = %p", jCtx.get());
+
jclass objclass = env->GetObjectClass(traceFunction);
jmethodID method =
env->GetMethodID(objclass, "trace", "(Landroid/tracing/perfetto/TracingContext;)V");
@@ -209,7 +218,9 @@
jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) {
const char* nativeString = env->GetStringUTFChars(name, 0);
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p, %s)", javaDataSource, nativeString);
PerfettoDataSource* dataSource = new PerfettoDataSource(env, javaDataSource, nativeString);
+ ALOG(LOG_DEBUG, LOG_TAG, "\tdatasource* = %p", dataSource);
env->ReleaseStringUTFChars(name, nativeString);
dataSource->incStrong((void*)nativeCreate);
@@ -218,33 +229,39 @@
}
void nativeDestroy(void* ptr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeCreate(%p)", ptr);
PerfettoDataSource* dataSource = reinterpret_cast<PerfettoDataSource*>(ptr);
dataSource->decStrong((void*)nativeCreate);
}
static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeGetFinalizer()");
return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy));
}
void nativeTrace(JNIEnv* env, jclass clazz, jlong dataSourcePtr, jobject traceFunctionInterface) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeTrace(%p)", (void*)dataSourcePtr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
datasource->trace(env, traceFunctionInterface);
}
void nativeFlush(JNIEnv* env, jclass clazz, jobject jCtx, jlong ctxPtr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p, %p)", jCtx, (void*)ctxPtr);
auto* ctx = reinterpret_cast<struct PerfettoDsTracerIterator*>(ctxPtr);
traceAllPendingPackets(env, jCtx, ctx);
PerfettoDsTracerFlush(ctx, nullptr, nullptr);
}
void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeFlushAll(%p)", (void*)ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ptr);
datasource->flushAll();
}
void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr,
int buffer_exhausted_policy) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeRegisterDataSource(%p)", (void*)datasource_ptr);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr);
struct PerfettoDsParams params = PerfettoDsParamsDefault();
@@ -267,6 +284,9 @@
auto* datasource_instance =
new PerfettoDataSourceInstance(env, java_data_source_instance.get(), inst_id);
+ ALOG(LOG_DEBUG, LOG_TAG, "on_setup_cb ds=%p, ds_instance=%p", datasource,
+ datasource_instance);
+
return static_cast<void*>(datasource_instance);
};
@@ -280,6 +300,8 @@
auto* tls_state = new TlsState(java_tls_state);
+ ALOG(LOG_DEBUG, LOG_TAG, "on_create_tls_cb ds=%p, tsl_state=%p", datasource, tls_state);
+
return static_cast<void*>(tls_state);
};
@@ -287,6 +309,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
TlsState* tls_state = reinterpret_cast<TlsState*>(ptr);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_delete_tls_cb %p", tls_state);
+
env->DeleteGlobalRef(tls_state->jobj);
delete tls_state;
};
@@ -299,6 +324,9 @@
jobject java_incr_state = datasource->createIncrementalStateGlobalRef(env, inst_id);
auto* incr_state = new IncrementalState(java_incr_state);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_create_incr_cb ds=%p, incr_state=%p", datasource, incr_state);
+
return static_cast<void*>(incr_state);
};
@@ -306,6 +334,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
IncrementalState* incr_state = reinterpret_cast<IncrementalState*>(ptr);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_delete_incr_cb incr_state=%p", incr_state);
+
env->DeleteGlobalRef(incr_state->jobj);
delete incr_state;
};
@@ -315,6 +346,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_start_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onStart(env);
};
@@ -323,6 +357,9 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_flush_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onFlush(env);
};
@@ -331,12 +368,18 @@
JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_stop_cb ds_instance=%p", datasource_instance);
+
datasource_instance->onStop(env);
};
params.on_destroy_cb = [](struct PerfettoDsImpl* ds_impl, void* user_arg,
void* inst_ctx) -> void {
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx);
+
+ ALOG(LOG_DEBUG, LOG_TAG, "on_destroy_cb ds_instance=%p", datasource_instance);
+
delete datasource_instance;
};
@@ -345,20 +388,28 @@
jobject nativeGetPerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeGetPerfettoInstanceLocked ds=%p, idx=%d", (void*)dataSourcePtr,
+ instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(
PerfettoDsImplGetInstanceLocked(datasource->dataSource.impl, instance_idx));
if (datasource_instance == nullptr) {
// datasource instance doesn't exist
+ ALOG(LOG_WARN, LOG_TAG,
+ "DS instance invalid!! nativeGetPerfettoInstanceLocked returning NULL");
return nullptr;
}
+ ALOG(LOG_DEBUG, LOG_TAG, "\tnativeGetPerfettoInstanceLocked got lock ds=%p, idx=%d",
+ (void*)dataSourcePtr, instance_idx);
return datasource_instance->GetJavaDataSourceInstance();
}
void nativeReleasePerfettoInstanceLocked(JNIEnv* env, jclass clazz, jlong dataSourcePtr,
PerfettoDsInstanceIndex instance_idx) {
+ ALOG(LOG_DEBUG, LOG_TAG, "nativeReleasePerfettoInstanceLocked got lock ds=%p, idx=%d",
+ (void*)dataSourcePtr, instance_idx);
sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(dataSourcePtr);
PerfettoDsImplReleaseInstanceLocked(datasource->dataSource.impl, instance_idx);
}
diff --git a/core/jni/android_tracing_PerfettoDataSource.h b/core/jni/android_tracing_PerfettoDataSource.h
index 61a7654..906d9f5 100644
--- a/core/jni/android_tracing_PerfettoDataSource.h
+++ b/core/jni/android_tracing_PerfettoDataSource.h
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_tracing_PerfettoDataSourceInstance.h b/core/jni/android_tracing_PerfettoDataSourceInstance.h
index ebb5259..be71cbb 100644
--- a/core/jni/android_tracing_PerfettoDataSourceInstance.h
+++ b/core/jni/android_tracing_PerfettoDataSourceInstance.h
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-#define LOG_TAG "Perfetto"
-
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <nativehelper/JNIHelp.h>
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index b39d5cf..1a86363 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -411,8 +411,8 @@
jniThrowNullPointerException(env, "pointerCoords");
return;
}
- pointerCoordsToNative(env, pointerCoordsObj,
- event->getXOffset(), event->getYOffset(), &rawPointerCoords[i]);
+ pointerCoordsToNative(env, pointerCoordsObj, event->getRawXOffset(), event->getRawYOffset(),
+ &rawPointerCoords[i]);
env->DeleteLocalRef(pointerCoordsObj);
}
@@ -622,6 +622,18 @@
return reinterpret_cast<jlong>(destEvent);
}
+static jlong android_view_MotionEvent_nativeSplit(jlong destNativePtr, jlong sourceNativePtr,
+ jint idBits) {
+ MotionEvent* destEvent = reinterpret_cast<MotionEvent*>(destNativePtr);
+ if (!destEvent) {
+ destEvent = new MotionEvent();
+ }
+ MotionEvent* sourceEvent = reinterpret_cast<MotionEvent*>(sourceNativePtr);
+ destEvent->splitFrom(*sourceEvent, static_cast<std::bitset<MAX_POINTER_ID + 1>>(idBits),
+ InputEvent::nextId());
+ return reinterpret_cast<jlong>(destEvent);
+}
+
static jint android_view_MotionEvent_nativeGetId(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
return event->getId();
@@ -723,14 +735,14 @@
return event->offsetLocation(deltaX, deltaY);
}
-static jfloat android_view_MotionEvent_nativeGetXOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawXOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getXOffset();
+ return event->getRawXOffset();
}
-static jfloat android_view_MotionEvent_nativeGetYOffset(jlong nativePtr) {
+static jfloat android_view_MotionEvent_nativeGetRawYOffset(jlong nativePtr) {
MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- return event->getYOffset();
+ return event->getRawYOffset();
}
static jfloat android_view_MotionEvent_nativeGetXPrecision(jlong nativePtr) {
@@ -837,6 +849,7 @@
// --------------- @CriticalNative ------------------
{"nativeCopy", "(JJZ)J", (void*)android_view_MotionEvent_nativeCopy},
+ {"nativeSplit", "(JJI)J", (void*)android_view_MotionEvent_nativeSplit},
{"nativeGetId", "(J)I", (void*)android_view_MotionEvent_nativeGetId},
{"nativeGetDeviceId", "(J)I", (void*)android_view_MotionEvent_nativeGetDeviceId},
{"nativeGetSource", "(J)I", (void*)android_view_MotionEvent_nativeGetSource},
@@ -858,8 +871,8 @@
{"nativeGetClassification", "(J)I",
(void*)android_view_MotionEvent_nativeGetClassification},
{"nativeOffsetLocation", "(JFF)V", (void*)android_view_MotionEvent_nativeOffsetLocation},
- {"nativeGetXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetXOffset},
- {"nativeGetYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetYOffset},
+ {"nativeGetRawXOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawXOffset},
+ {"nativeGetRawYOffset", "(J)F", (void*)android_view_MotionEvent_nativeGetRawYOffset},
{"nativeGetXPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetXPrecision},
{"nativeGetYPrecision", "(J)F", (void*)android_view_MotionEvent_nativeGetYPrecision},
{"nativeGetXCursorPosition", "(J)F",
diff --git a/core/proto/android/hardware/sensorprivacy.proto b/core/proto/android/hardware/sensorprivacy.proto
index e368c6a..53aa710 100644
--- a/core/proto/android/hardware/sensorprivacy.proto
+++ b/core/proto/android/hardware/sensorprivacy.proto
@@ -91,9 +91,7 @@
enum StateType {
ENABLED = 1;
DISABLED = 2;
- AUTO_DRIVER_ASSISTANCE_HELPFUL_APPS = 3;
- AUTO_DRIVER_ASSISTANCE_REQUIRED_APPS = 4;
- AUTO_DRIVER_ASSISTANCE_APPS = 5;
+ ENABLED_EXCEPT_ALLOWLISTED_APPS = 3;
}
// DEPRECATED
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
index 5a18d9e..b75d545 100644
--- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
+++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto
@@ -39,7 +39,7 @@
optional string cur_token = 14;
optional int32 cur_token_display_id = 15;
optional bool system_ready = 16;
- optional int32 last_switch_user_id = 17;
+ reserved 17; // deprecated last_switch_user_id
optional bool have_connection = 18;
optional bool bound_to_method = 19;
optional bool is_interactive = 20;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 487b5be..1acdc75 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6772,13 +6772,6 @@
<permission android:name="android.permission.USE_BIOMETRIC_INTERNAL"
android:protectionLevel="signature" />
- <!-- Allows privileged apps to access the background face authentication.
- @SystemApi
- @FlaggedApi("android.hardware.biometrics.face_background_authentication")
- @hide -->
- <permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION"
- android:protectionLevel="signature|privileged" />
-
<!-- Allows the system to control the BiometricDialog (SystemUI). Reserved for the system. @hide -->
<permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG"
android:protectionLevel="signature" />
@@ -7955,12 +7948,12 @@
android:protectionLevel="signature|privileged" />
- <!-- @SystemApi Allows an app to bind the on-device trusted service.
+ <!-- @SystemApi Allows an app to bind the on-device sandboxed service.
<p>Protection level: signature|privileged
@hide
@FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence")
-->
- <permission android:name="android.permission.BIND_ON_DEVICE_TRUSTED_SERVICE"
+ <permission android:name="android.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE"
android:protectionLevel="signature"/>
@@ -8621,6 +8614,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.system.virtualmachine.SecretkeeperJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE">
+ </service>
+
<service android:name="com.android.server.PruneInstantAppsJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
@@ -8675,7 +8672,7 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
- <service android:name="com.android.server.companion.InactiveAssociationsRemovalService"
+ <service android:name="com.android.server.companion.association.InactiveAssociationsRemovalService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index df8158d..6e804c0 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -146,7 +146,7 @@
<item name="windowAnimationStyle">@style/Animation.InputMethod</item>
<item name="imeFullscreenBackground">?colorBackground</item>
<item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
- <item name="windowSwipeToDismiss">false</item>
+ <item name="windowSwipeToDismiss">true</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c882938..d89f236 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -301,7 +301,9 @@
granted to the system companion device manager service -->
<flag name="companion" value="0x800000" />
<!-- Additional flag from base permission type: this permission will be granted to the
- retail demo app, as defined by the OEM. -->
+ retail demo app, as defined by the OEM.
+ This flag has been replaced by the retail demo role and is a no-op since Android V.
+ -->
<flag name="retailDemo" value="0x1000000" />
<!-- Additional flag from base permission type: this permission will be granted to the
recents app. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8edf42a..a0cb705 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1608,7 +1608,10 @@
<fraction name="config_autoBrightnessAdjustmentMaxGamma">300%</fraction>
<!-- If we allow automatic adjustment of screen brightness while dozing, how many times we want
- to reduce it to preserve the battery. Value of 100% means no scaling. -->
+ to reduce it to preserve the battery. Value of 100% means no scaling. Not used if there is
+ a designated auto-brightness doze mapping defined in Display Device Config.
+ Also used to scale the brightness for the doze mode when auto-brightness is disabled if
+ there is an offload session present. -->
<fraction name="config_screenAutoBrightnessDozeScaleFactor">100%</fraction>
<!-- When the screen is turned on, the previous estimate of the ambient light level at the time
@@ -3348,26 +3351,23 @@
<string name="config_carrierAppInstallDialogComponent" translatable="false"
>com.android.simappdialog/com.android.simappdialog.InstallCarrierAppActivity</string>
- <!-- Name of the default framework dialog that is used to get or save an app credential.
+ <!-- Name of the fallback CredentialManager dialog that is used to get or save an app
+ credential.
- This UI should be always launch-able and is used as a fallback when an oem replacement activity
- (defined at config_oemCredentialManagerDialogComponent) is undefined / not found. -->
- <string name="config_credentialManagerDialogComponent" translatable="false"
+ If empty, no fallback will be used. IMPORTANT: In that case the OEM dialog value specified in
+ config_oemCredentialManagerDialogComponent must always launch-able. Otherwise, the
+ CredentialManager API contract is broken.
+
+ If specified, this UI should be always launch-able. It will be used as a fallback when the OEM
+ dialog value specified in config_oemCredentialManagerDialogComponent) is undefined / not
+ found. -->
+ <string name="config_fallbackCredentialManagerDialogComponent" translatable="false"
>com.android.credentialmanager/com.android.credentialmanager.CredentialSelectorActivity</string>
- <!-- Whether to allow the credential selector activity to be replaced by an activity at
- run-time (restricted to the privileged activity specified by
- config_credentialSelectorActivityName).
-
- When disabled, the fallback activity defined at
- config_credentialManagerDialogComponent will be used instead. -->
- <bool name="config_enableOemCredentialManagerDialogComponent" translatable="false">true</bool>
<!-- Fully qualified activity name providing the credential selector UI, that serves the
- CredentialManager APIs.
+ CredentialManager APIs. Must be a system app component.
- Used only when config_enableOemCredentialManagerDialogComponent is true.
-
- If the activity specified cannot be found or launched, then the fallback activity defined at
- config_credentialManagerDialogComponent will be used instead. -->
+ If empty, or if this activity specified cannot be found or launched, then the fallback activity
+ defined at config_fallbackCredentialManagerDialogComponent will be used instead. -->
<string name="config_oemCredentialManagerDialogComponent" translatable="false"></string>
<!-- Name of the broadcast receiver that is used to receive provider change events -->
@@ -3554,10 +3554,6 @@
<string name="config_keyguardComponent" translatable="false"
>com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>
- <!-- Screen record dialog component -->
- <string name="config_screenRecorderComponent" translatable="false"
- >com.android.systemui/com.android.systemui.screenrecord.ScreenRecordDialog</string>
-
<!-- The component name of a special dock app that merely launches a dream.
We don't want to launch this app when docked because it causes an unnecessary
activity transition. We just want to start the dream. -->
@@ -4680,8 +4676,8 @@
<!-- The component name for the default system on-device intelligence service, -->
<string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string>
- <!-- The component name for the default system on-device trusted inference service. -->
- <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string>
+ <!-- The component name for the default system on-device sandboxed inference service. -->
+ <string name="config_defaultOnDeviceSandboxedInferenceService" translatable="false"></string>
<!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
wearable sensing. -->
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 5e3e1b0..6e56fe2 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -334,4 +334,9 @@
<bool name="config_enable_cellular_on_boot_default">true</bool>
<java-symbol type="bool" name="config_enable_cellular_on_boot_default" />
+ <!-- Defines metrics pull cooldown period. The default cooldown period is 23 hours,
+ some Telephony metrics need to be pulled more frequently -->
+ <integer name="config_metrics_pull_cooldown_millis">82800000</integer>
+ <java-symbol type="integer" name="config_metrics_pull_cooldown_millis" />
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 59066eb..c7e08e2 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5452,7 +5452,7 @@
<string name="set_up_screen_lock_title">Set a screen lock</string>
<!-- Action label for the dialog prompting the user to set up a screen lock [CHAR LIMIT=30] -->
<string name="set_up_screen_lock_action_label">Set screen lock</string>
- <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=30] -->
+ <!-- Message shown in the dialog prompting the user to set up a screen lock to access private space [CHAR LIMIT=120] -->
<string name="private_space_set_up_screen_lock_message">To use your private space, set a screen lock on this device</string>
<!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
@@ -6434,4 +6434,6 @@
<string name="satellite_notification_open_message">Open Messages</string>
<!-- Invoke Satellite setting activity of Settings -->
<string name="satellite_notification_how_it_works">How it works</string>
+ <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
+ <string name="unarchival_session_app_label">Pending...</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a180467..4b71654 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -375,7 +375,6 @@
<java-symbol type="string" name="config_recentsComponentName" />
<java-symbol type="string" name="config_systemUIServiceComponent" />
<java-symbol type="string" name="config_controlsPackage" />
- <java-symbol type="string" name="config_screenRecorderComponent" />
<java-symbol type="string" name="config_somnambulatorComponent" />
<java-symbol type="string" name="config_screenshotAppClipsServiceComponent" />
<java-symbol type="string" name="config_screenshotServiceComponent" />
@@ -2297,8 +2296,7 @@
<java-symbol type="string" name="config_customVpnAlwaysOnDisconnectedDialogComponent" />
<java-symbol type="string" name="config_platformVpnConfirmDialogComponent" />
<java-symbol type="string" name="config_carrierAppInstallDialogComponent" />
- <java-symbol type="string" name="config_credentialManagerDialogComponent" />
- <java-symbol type="bool" name="config_enableOemCredentialManagerDialogComponent" />
+ <java-symbol type="string" name="config_fallbackCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_oemCredentialManagerDialogComponent" />
<java-symbol type="string" name="config_credentialManagerReceiverComponent" />
<java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
@@ -3918,7 +3916,7 @@
<java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
<java-symbol type="string" name="config_defaultWearableSensingService" />
<java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" />
- <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" />
+ <java-symbol type="string" name="config_defaultOnDeviceSandboxedInferenceService" />
<java-symbol type="string" name="config_retailDemoPackage" />
<java-symbol type="string" name="config_retailDemoPackageSignature" />
@@ -5375,4 +5373,6 @@
<java-symbol type="string" name="config_defaultContextualSearchKey" />
<java-symbol type="string" name="config_defaultContextualSearchEnabled" />
<java-symbol type="string" name="config_defaultContextualSearchLegacyEnabled" />
+
+ <java-symbol type="string" name="unarchival_session_app_label" />
</resources>
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 61e6a36..7d740ef 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -88,6 +88,9 @@
<!-- Colombia: 1-6 digits (not confirmed) -->
<shortcode country="co" pattern="\\d{1,6}" free="890350|908160|892255|898002|898880|899960|899948|87739|85517|491289" />
+ <!-- Costa Rica -->
+ <shortcode country="cr" pattern="\\d{1,6}" free="466453" />
+
<!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
@@ -143,6 +146,9 @@
<!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
<shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}|12115" />
+ <!-- Guatemala -->
+ <shortcode country="gt" pattern="\\d{1,6}" free="466453" />
+
<!-- Croatia -->
<shortcode country="hr" pattern="\\d{1,5}" free="13062" />
@@ -241,10 +247,10 @@
<shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" />
<!-- Pakistan -->
- <shortcode country="pk" pattern="\\d{1,5}" free="2057" />
+ <shortcode country="pk" pattern="\\d{1,5}" free="2057|9092" />
<!-- Palestine: 5 digits, known premium codes listed -->
- <shortcode country="ps" pattern="\\d{1,5}" free="37477" />
+ <shortcode country="ps" pattern="\\d{1,5}" free="37477|6681" />
<!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
<shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}|8012|80921" />
@@ -324,4 +330,7 @@
<!-- South Africa -->
<shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056" />
+ <!-- Zimbabwe -->
+ <shortcode country="zw" pattern="\\d{1,5}" free="33679" />
+
</shortcodes>
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index 15bb66b..8d9fad9 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -90,7 +90,7 @@
private static final long TEST_HD_FREQUENCY_VALUE = 95_300;
private static final long TEST_HD_STATION_ID_EXT_VALUE = 0x100000001L
| (TEST_HD_FREQUENCY_VALUE << 36);
- private static final long TEST_HD_LOCATION_VALUE = 0x89CC8E06CCB9ECL;
+ private static final long TEST_HD_LOCATION_VALUE = 0x4E647007665CF6L;
private static final long TEST_VENDOR_ID_VALUE = 9_901;
private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
diff --git a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
index 37a499a..6cc5485 100644
--- a/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
+++ b/core/tests/bugreports/src/com/android/os/bugreports/tests/BugreportManagerTest.java
@@ -110,8 +110,6 @@
Paths.get("/data/misc/wmtrace/ime_trace_service.winscope"),
Paths.get("/data/misc/wmtrace/wm_trace.winscope"),
Paths.get("/data/misc/wmtrace/wm_log.winscope"),
- Paths.get("/data/misc/wmtrace/wm_transition_trace.winscope"),
- Paths.get("/data/misc/wmtrace/shell_transition_trace.winscope"),
};
private Handler mHandler;
@@ -257,6 +255,38 @@
assertThatAllFileContentsAreDifferent(preDumpedTraceFiles, actualTraceFiles);
}
+ @LargeTest
+ @Test
+ public void noPreDumpData_then_fullWithUsePreDumpFlag_ignoresFlag() throws Exception {
+ startPreDumpedUiTraces();
+
+ mBrm.preDumpUiData();
+ waitTillDumpstateExitedOrTimeout();
+
+ // Simulate lost of pre-dumped data.
+ // For example it can happen in this scenario:
+ // 1. Pre-dump data
+ // 2. Start bugreport + "use pre-dump" flag (USE AND REMOVE THE PRE-DUMP FROM DISK)
+ // 3. Start bugreport + "use pre-dump" flag (NO PRE-DUMP AVAILABLE ON DISK)
+ removeFilesIfNeeded(UI_TRACES_PREDUMPED);
+
+ // Start bugreport with "use predump" flag. Because the pre-dumped data is not available
+ // the flag will be ignored and data will be dumped as in normal flow.
+ BugreportCallbackImpl callback = new BugreportCallbackImpl();
+ mBrm.startBugreport(mBugreportFd, null, fullWithUsePreDumpFlag(), mExecutor,
+ callback);
+ shareConsentDialog(ConsentReply.ALLOW);
+ waitTillDoneOrTimeout(callback);
+
+ stopPreDumpedUiTraces();
+
+ assertThat(callback.isDone()).isTrue();
+ assertThat(mBugreportFile.length()).isGreaterThan(0L);
+ assertFdsAreClosed(mBugreportFd);
+
+ assertThatBugreportContainsFiles(UI_TRACES_PREDUMPED);
+ }
+
@Test
public void simultaneousBugreportsNotAllowed() throws Exception {
// Start bugreport #1
@@ -506,9 +536,6 @@
InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
"cmd window tracing start"
);
- InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
- "service call SurfaceFlinger 1025 i32 1"
- );
}
private static void stopPreDumpedUiTraces() {
@@ -611,6 +638,14 @@
return files;
}
+ private static void removeFilesIfNeeded(Path[] paths) throws Exception {
+ for (Path path : paths) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+ "rm -f " + path.toString()
+ );
+ }
+ }
+
private static ParcelFileDescriptor parcelFd(File file) throws Exception {
return ParcelFileDescriptor.open(file,
ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_APPEND);
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e72beee..24031cad 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -101,6 +101,7 @@
"flickerlib-trace_processor_shell",
"mockito-target-extended-minus-junit4",
"TestParameterInjector",
+ "android.content.res.flags-aconfig-java",
],
libs: [
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
index 866e1a9..5029212 100644
--- a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/horizontal_scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <HorizontalScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="match_parent"
+ android:id="@+id/horizontal_scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</HorizontalScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </HorizontalScrollView>
+
+ <view
+ class="android.widget.HorizontalScrollViewFunctionalTest$MyHorizontalScrollView"
+ android:id="@+id/my_horizontal_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
index 61fabf8..db8cd02 100644
--- a/core/tests/coretests/res/layout/activity_scroll_view.xml
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -14,105 +14,150 @@
~ limitations under the License
-->
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:id="@+id/scroll_view">
+ android:orientation="vertical">
- <LinearLayout
+ <ScrollView
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:layout_height="match_parent"
+ android:id="@+id/scroll_view">
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#F00"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#880"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#F00"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#0F0"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#880"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#088"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#0F0"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#00F"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#088"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- <View
- android:background="#808"
- android:layout_width="100dp"
- android:layout_height="100dp" />
+ <View
+ android:background="#00F"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
- </LinearLayout>
-</ScrollView>
+ <View
+ android:background="#808"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ </LinearLayout>
+ </ScrollView>
+
+ <view
+ class="android.widget.ScrollViewFunctionalTest$MyScrollView"
+ android:id="@+id/my_scroll_view"
+ android:layout_width="90dp"
+ android:layout_height="90dp"
+ android:background="#FFF"
+ android:defaultFocusHighlightEnabled="false">
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <View
+ android:background="#00F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0FF"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#0F0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#FF0"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F00"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ <View
+ android:background="#F0F"
+ android:layout_width="90dp"
+ android:layout_height="50dp"/>
+ </LinearLayout>
+ </view>
+</LinearLayout>
diff --git a/core/tests/coretests/res/layout/view_velocity_test.xml b/core/tests/coretests/res/layout/view_velocity_test.xml
new file mode 100644
index 0000000..98154a4
--- /dev/null
+++ b/core/tests/coretests/res/layout/view_velocity_test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frameLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:id="@+id/moving_view"
+ android:layout_width="50dp"
+ android:layout_height="50dp" />
+</FrameLayout>
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index d115bf3..927c67c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -21,16 +21,22 @@
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
+import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
@@ -46,6 +52,7 @@
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.ConfigurationChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -81,11 +90,14 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -103,11 +115,17 @@
// few sequence numbers the framework used to launch the test activity.
private static final int BASE_SEQ = 10000000;
- @Rule
+ @Rule(order = 0)
public final ActivityTestRule<TestActivity> mActivityTestRule =
new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
+ @Rule(order = 1)
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+
private WindowTokenClientController mOriginalWindowTokenClientController;
private Configuration mOriginalAppConfig;
@@ -115,6 +133,8 @@
@Before
public void setup() {
+ MockitoAnnotations.initMocks(this);
+
// Keep track of the original controller, so that it can be used to restore in tearDown()
// when there is override in some test cases.
mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@
mCreatedVirtualDisplays = null;
}
WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
InstrumentationRegistry.getInstrumentation().runOnMainSync(
() -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
}
@@ -783,6 +805,101 @@
verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
}
+ @Test
+ public void testActivityWindowInfoChanged_activityLaunch() {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityClientRecord.getActivityWindowInfo());
+
+ final Configuration currentConfig = activity.getResources().getConfiguration();
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
+ activity.getActivityToken(), null, null, 0,
+ new MergedConfiguration(currentConfig, currentConfig),
+ false /* preserveWindow */, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(relaunchItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
+ activityWindowInfo);
+ }
+
+ @Test
+ public void testActivityWindowInfoChanged_activityConfigurationChanged()
+ throws RemoteException {
+ mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
+ mActivityWindowInfoListener);
+
+ final Activity activity = mActivityTestRule.launchActivity(new Intent());
+ final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ clearInvocations(mActivityWindowInfoListener);
+ final Configuration config = new Configuration(activity.getResources().getConfiguration());
+ config.seq++;
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo);
+ final ClientTransaction transaction = newTransaction(activity);
+ transaction.addTransactionItem(activityConfigurationChangeItem);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
+ activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
+ activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
+ config.seq++;
+ final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
+ ActivityConfigurationChangeItem.obtain(
+ activity.getActivityToken(), config, activityWindowInfo2);
+ final ClientTransaction transaction2 = newTransaction(activity);
+ transaction2.addTransactionItem(activityConfigurationChangeItem2);
+ appThread.scheduleTransaction(transaction);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ // The same ActivityWindowInfo won't trigger duplicated callback.
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
+
/**
* Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
* Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
@@ -871,7 +988,7 @@
@NonNull
private static ClientTransaction newStopTransaction(@NonNull Activity activity) {
final StopActivityItem stopStateRequest = StopActivityItem.obtain(
- activity.getActivityToken(), 0 /* configChanges */);
+ activity.getActivityToken());
final ClientTransaction transaction = newTransaction(activity);
transaction.addTransactionItem(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 4db5d1b..9907397 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -136,7 +136,7 @@
@Test
public void testDestroyActivityItem_preExecute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.preExecute(mHandler);
assertEquals(1, mActivitiesToBeDestroyed.size());
@@ -146,7 +146,7 @@
@Test
public void testDestroyActivityItem_postExecute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.preExecute(mHandler);
item.postExecute(mHandler, mPendingActions);
@@ -156,11 +156,11 @@
@Test
public void testDestroyActivityItem_execute() {
final DestroyActivityItem item = DestroyActivityItem
- .obtain(mActivityToken, false /* finished */, 123 /* configChanges */);
+ .obtain(mActivityToken, false /* finished */);
item.execute(mHandler, mActivityClientRecord, mPendingActions);
verify(mHandler).handleDestroyActivity(eq(mActivityClientRecord), eq(false) /* finishing */,
- eq(123) /* configChanges */, eq(false) /* getNonConfigInstance */, any());
+ eq(false) /* getNonConfigInstance */, any());
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 213fd7b..77d31a5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -22,21 +22,29 @@
import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
+import android.window.ActivityWindowInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +52,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.BiConsumer;
+
/**
* Tests for {@link ClientTransactionListenerController}.
*
@@ -62,6 +72,10 @@
private IDisplayManager mIDisplayManager;
@Mock
private DisplayManager.DisplayListener mListener;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private IBinder mActivityToken;
private DisplayManagerGlobal mDisplayManager;
private Handler mHandler;
@@ -91,4 +105,24 @@
verify(mListener).onDisplayChanged(123);
}
+
+ @Test
+ public void testActivityWindowInfoChangedListener() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
+ activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
+ new Rect(0, 0, 1000, 1000));
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);
+
+ clearInvocations(mActivityWindowInfoListener);
+ mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);
+
+ verify(mActivityWindowInfoListener, never()).accept(any(), any());
+ }
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 31ea675..584fe16 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,7 +98,7 @@
@Test
public void testRecycleDestroyActivityItem() {
- testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true, 117));
+ testRecycle(() -> DestroyActivityItem.obtain(mActivityToken, true));
}
@Test
@@ -169,7 +169,7 @@
@Test
public void testRecyclePauseActivityItemItem() {
- testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, 5, true, true));
+ testRecycle(() -> PauseActivityItem.obtain(mActivityToken, true, true, true, true));
}
@Test
@@ -185,7 +185,7 @@
@Test
public void testRecycleStopItem() {
- testRecycle(() -> StopActivityItem.obtain(mActivityToken, 4));
+ testRecycle(() -> StopActivityItem.obtain(mActivityToken));
}
@Test
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index adb6f2a..935bc75 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -306,7 +306,7 @@
final IBinder token = mock(IBinder.class);
final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */);
destroyTransaction.addTransactionItem(
- DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
+ DestroyActivityItem.obtain(token, false /* finished */));
destroyTransaction.preExecute(mTransactionHandler);
// The activity should be added to to-be-destroyed container.
assertEquals(1, activitiesToBeDestroyed.size());
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 75347bf..d451fe5 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -155,8 +155,7 @@
@Test
public void testDestroy() {
- DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */,
- 135 /* configChanges */);
+ DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -244,8 +243,7 @@
public void testPause() {
// Write to parcel
PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
- true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
- true /* autoEnteringPip */);
+ true /* userLeaving */, true /* dontReport */, true /* autoEnteringPip */);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -272,7 +270,7 @@
@Test
public void testStop() {
// Write to parcel
- StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */);
+ StopActivityItem item = StopActivityItem.obtain(mActivityToken);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -305,8 +303,7 @@
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
mActivityToken, config(), new ActivityWindowInfo());
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addTransactionItem(callback1);
@@ -351,8 +348,7 @@
mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
// Write to parcel
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken);
ClientTransaction transaction = ClientTransaction.obtain(null /* client */);
transaction.addTransactionItem(lifecycleRequest);
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 4a9cb71..0c1e879 100644
--- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
+++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
@@ -18,34 +18,52 @@
import android.annotation.NonNull;
import android.app.ResourcesManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.LocaleList;
import android.platform.test.annotations.Postsubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Display;
import android.view.DisplayAdjustments;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Postsubmit
+@RunWith(AndroidJUnit4.class)
public class ResourcesManagerTest extends TestCase {
private static final int SECONDARY_DISPLAY_ID = 1;
private static final String APP_ONE_RES_DIR = "app_one.apk";
private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
private static final String APP_TWO_RES_DIR = "app_two.apk";
private static final String LIB_RES_DIR = "lib.apk";
+ private static final String TEST_LIB = "com.android.frameworks.coretests.bdr_helper_app1";
private ResourcesManager mResourcesManager;
private Map<Integer, DisplayMetrics> mDisplayMetricsMap;
+ private PackageManager mPackageManager;
- @Override
- protected void setUp() throws Exception {
+ @Before
+ public void setUp() throws Exception {
super.setUp();
mDisplayMetricsMap = new HashMap<>();
@@ -93,8 +111,14 @@
return mDisplayMetricsMap.get(displayId);
}
};
+
+ mPackageManager = InstrumentationRegistry.getContext().getPackageManager();
}
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Test
@SmallTest
public void testMultipleCallsWithIdenticalParametersCacheReference() {
Resources resources = mResourcesManager.getResources(
@@ -109,6 +133,7 @@
assertSame(resources.getImpl(), newResources.getImpl());
}
+ @Test
@SmallTest
public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
Resources resources = mResourcesManager.getResources(
@@ -125,6 +150,7 @@
assertNotSame(resources, newResources);
}
+ @Test
@SmallTest
public void testAddingASplitCreatesANewImpl() {
Resources resources1 = mResourcesManager.getResources(
@@ -142,6 +168,7 @@
assertNotSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testUpdateConfigurationUpdatesAllAssetManagers() {
Resources resources1 = mResourcesManager.getResources(
@@ -187,6 +214,7 @@
assertEquals(expectedConfig, resources3.getConfiguration());
}
+ @Test
@SmallTest
public void testTwoActivitiesWithIdenticalParametersShareImpl() {
Binder activity1 = new Binder();
@@ -208,6 +236,7 @@
assertSame(resources1.getImpl(), resources2.getImpl());
}
+ @Test
@SmallTest
public void testThemesGetUpdatedWithNewImpl() {
Binder activity1 = new Binder();
@@ -237,6 +266,7 @@
assertTrue(value.data != 0);
}
+ @Test
@SmallTest
public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
Binder activity1 = new Binder();
@@ -286,6 +316,7 @@
assertEquals(expectedConfig2, resources2.getConfiguration());
}
+ @Test
@SmallTest
public void testChangingActivityDisplayDoesntOverrideDisplayRequestedByResources() {
Binder activity = new Binder();
@@ -322,4 +353,101 @@
assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels,
defaultDisplayResources.getDisplayMetrics().widthPixels);
}
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testExistingResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ // Create a Resources before register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+ ResourcesImpl oriResImpl = resources.getImpl();
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ assertNotSame(oriResImpl, resources.getImpl());
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ @Test
+ @SmallTest
+ @RequiresFlagsEnabled(Flags.FLAG_REGISTER_RESOURCE_PATHS)
+ public void testNewResourcesAfterResourcePathsRegistration()
+ throws PackageManager.NameNotFoundException {
+ // Inject ResourcesManager instance from this test to the ResourcesManager class so that all
+ // the static method can interact with this test smoothly.
+ ResourcesManager oriResourcesManager = ResourcesManager.getInstance();
+ ResourcesManager.setInstance(mResourcesManager);
+
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(TEST_LIB, 0);
+ Resources.registerResourcePaths(TEST_LIB, appInfo);
+
+ // Create a Resources after register resources' paths for a package.
+ Resources resources = mResourcesManager.getResources(
+ null, APP_ONE_RES_DIR, null, null, null, null, null, null,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null);
+ assertNotNull(resources);
+
+ String[] resourcePaths = appInfo.getAllApkPaths();
+ resourcePaths = removeDuplicates(resourcePaths);
+ ApkAssets[] loadedAssets = resources.getAssets().getApkAssets();
+ assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets));
+
+ // Package resources' paths should be cached in ResourcesManager.
+ assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance()
+ .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths()));
+
+ // Revert the ResourcesManager instance back.
+ ResourcesManager.setInstance(oriResourcesManager);
+ }
+
+ private static boolean allResourcePathsLoaded(String[] resourcePaths, ApkAssets[] loadedAsset) {
+ for (int i = 0; i < resourcePaths.length; i++) {
+ if (!resourcePaths[i].endsWith(".apk")) {
+ continue;
+ }
+ boolean found = false;
+ for (int j = 0; j < loadedAsset.length; j++) {
+ if (loadedAsset[j].getAssetPath().equals(resourcePaths[i])) {
+ found = true;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String[] removeDuplicates(String[] paths) {
+ var pathList = new ArrayList<String>();
+ var pathSet = new ArraySet<String>();
+ final int pathsLen = paths.length;
+ for (int i = 0; i < pathsLen; i++) {
+ if (pathSet.add(paths[i])) {
+ pathList.add(paths[i]);
+ }
+ }
+ return pathList.toArray(new String[0]);
+ }
}
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 34f5841..3a872b5 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -18,7 +18,6 @@
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS;
-import static android.hardware.biometrics.Flags.FLAG_FACE_BACKGROUND_AUTHENTICATION;
import static com.google.common.truth.Truth.assertThat;
@@ -36,15 +35,12 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
-import android.hardware.biometrics.BiometricPrompt;
import android.os.CancellationSignal;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import com.android.internal.R;
@@ -62,7 +58,6 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -83,8 +78,6 @@
@Mock
private FaceManager.AuthenticationCallback mAuthCallback;
@Mock
- private BiometricPrompt.AuthenticationCallback mBioAuthCallback;
- @Mock
private FaceManager.EnrollmentCallback mEnrollmentCallback;
@Mock
private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
@@ -98,16 +91,13 @@
private TestLooper mLooper;
private Handler mHandler;
private FaceManager mFaceManager;
- private Executor mExecutor;
@Before
public void setUp() throws Exception {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
- mExecutor = new HandlerExecutor(mHandler);
when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
- when(mContext.getMainExecutor()).thenReturn(mExecutor);
when(mContext.getOpPackageName()).thenReturn(PACKAGE_NAME);
when(mContext.getAttributionTag()).thenReturn(ATTRIBUTION_TAG);
when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
@@ -169,19 +159,6 @@
}
@Test
- @RequiresFlagsEnabled(FLAG_FACE_BACKGROUND_AUTHENTICATION)
- public void authenticateInBackground_errorWhenUnavailable() throws Exception {
- when(mService.authenticateInBackground(any(), anyLong(), any(), any()))
- .thenThrow(new RemoteException());
-
- mFaceManager.authenticateInBackground(mExecutor, null, new CancellationSignal(),
- mBioAuthCallback);
- mLooper.dispatchAll();
-
- verify(mBioAuthCallback).onAuthenticationError(eq(FACE_ERROR_HW_UNAVAILABLE), any());
- }
-
- @Test
public void enrollment_errorWhenFaceEnrollmentExists() throws RemoteException {
when(mResources.getInteger(R.integer.config_faceMaxTemplatesPerUser)).thenReturn(1);
when(mService.getEnrolledFaces(anyInt(), anyInt(), anyString()))
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
new file mode 100644
index 0000000..90a8c5c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewFrameRateTest {
+
+ @Rule
+ public ActivityTestRule<ViewCaptureTestActivity> mActivityRule = new ActivityTestRule<>(
+ ViewCaptureTestActivity.class);
+
+ private Activity mActivity;
+ private View mMovingView;
+ private ViewRootImpl mViewRoot;
+
+ @Before
+ public void setUp() throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ mActivityRule.runOnUiThread(() -> {
+ mActivity.setContentView(R.layout.view_velocity_test);
+ mMovingView = mActivity.findViewById(R.id.moving_view);
+ });
+ ViewParent parent = mActivity.getWindow().getDecorView().getParent();
+ while (parent instanceof View) {
+ parent = parent.getParent();
+ }
+ mViewRoot = (ViewRootImpl) parent;
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void frameRateChangesWhenContentMoves() {
+ mMovingView.offsetLeftAndRight(100);
+ float frameRate = mViewRoot.getPreferredFrameRate();
+ assertTrue(frameRate > 0);
+ }
+
+ @UiThreadTest
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void firstFrameNoMovement() {
+ assertEquals(0f, mViewRoot.getPreferredFrameRate(), 0f);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void touchBoostDisable() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ long now = SystemClock.uptimeMillis();
+ MotionEvent down = MotionEvent.obtain(
+ /* downTime */ now,
+ /* eventTime */ now,
+ /* action */ MotionEvent.ACTION_DOWN,
+ /* x */ 0f,
+ /* y */ 0f,
+ /* metaState */ 0
+ );
+ mActivity.dispatchTouchEvent(down);
+ mMovingView.offsetLeftAndRight(10);
+ });
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ });
+
+ mActivityRule.runOnUiThread(() -> {
+ assertFalse(mViewRoot.getIsTouchBoosting());
+ });
+ }
+
+ private void waitForFrameRateCategoryToSettle() throws Throwable {
+ for (int i = 0; i < 5; i++) {
+ final CountDownLatch drawLatch = new CountDownLatch(1);
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch::countDown);
+ });
+
+ assertTrue(drawLatch.await(1, TimeUnit.SECONDS));
+ }
+ }
+
+ @Test
+ public void noVelocityUsesCategorySmall() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (40 * density);
+ layoutParams.width = (int) (40 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ layoutParams.width = (int) (10 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (10 * density);
+ layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a normal category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_NORMAL,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryLargeWidth() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) (40 * density);
+ layoutParams.width = (int) Math.ceil(41 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a high category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+
+ @Test
+ public void noVelocityUsesCategoryLargeHeight() throws Throwable {
+ final CountDownLatch drawLatch1 = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ float density = mActivity.getResources().getDisplayMetrics().density;
+ ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
+ layoutParams.height = (int) Math.ceil(41 * density);
+ layoutParams.width = (int) (40 * density);
+ mMovingView.setLayoutParams(layoutParams);
+ mMovingView.getViewTreeObserver().addOnDrawListener(drawLatch1::countDown);
+ });
+
+ assertTrue(drawLatch1.await(1, TimeUnit.SECONDS));
+ waitForFrameRateCategoryToSettle();
+
+ // Now that it is small, any invalidation should have a high category
+ mActivityRule.runOnUiThread(() -> {
+ mMovingView.invalidate();
+ assertEquals(Surface.FRAME_RATE_CATEGORY_HIGH,
+ mViewRoot.getPreferredFrameRateCategory());
+ });
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 1a242ef..2544fcb 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
@@ -796,6 +797,38 @@
}
/**
+ * When velocity of a View is not equal to 0, we call setFrameRateCategory with HIGH.
+ * Also, we shouldn't call setFrameRate.
+ */
+ @Test
+ @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, FLAG_VIEW_VELOCITY_API})
+ public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() {
+ View view = new View(sContext);
+ WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+ wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+ wmlp.width = 1;
+ wmlp.height = 1;
+
+ sInstrumentation.runOnMainSync(() -> {
+ WindowManager wm = sContext.getSystemService(WindowManager.class);
+ wm.addView(view, wmlp);
+ });
+ sInstrumentation.waitForIdleSync();
+
+ ViewRootImpl viewRootImpl = view.getViewRootImpl();
+
+ sInstrumentation.runOnMainSync(() -> {
+ assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+ view.setFrameContentVelocity(100);
+ view.invalidate();
+ assertTrue(viewRootImpl.getPreferredFrameRate() > 0);
+ });
+ sInstrumentation.waitForIdleSync();
+ assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ assertEquals(viewRootImpl.getLastPreferredFrameRate(), 0 , 0.1);
+ }
+
+ /**
* We should boost the frame rate if the value of mInsetsAnimationRunning is true.
*/
@Test
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index df212eb..cd38bd6 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,21 @@
public class HorizontalScrollViewFunctionalTest {
private HorizontalScrollViewActivity mActivity;
private HorizontalScrollView mHorizontalScrollView;
+ private MyHorizontalScrollView mMyHorizontalScrollView;
@Rule
public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
HorizontalScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+ mMyHorizontalScrollView =
+ (MyHorizontalScrollView) mActivity.findViewById(R.id.my_horizontal_scroll_view);
}
@Test
@@ -79,6 +92,22 @@
assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyHorizontalScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyHorizontalScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +121,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyHorizontalScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyHorizontalScrollView(Context context) {
+ super(context);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
index 4f722ce..6ab77dc 100644
--- a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -355,7 +355,7 @@
}
@Override
- public RemoteViews.RemoteCollectionItems getRemoteCollectionItems() {
+ public RemoteViews.RemoteCollectionItems getRemoteCollectionItems(int capSize) {
RemoteViews.RemoteCollectionItems.Builder itemsBuilder =
new RemoteViews.RemoteCollectionItems.Builder();
itemsBuilder.setHasStableIds(hasStableIds())
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
index 109c808..a60b2a13 100644
--- a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -16,11 +16,17 @@
package android.widget;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.AttributeSet;
import android.util.PollingCheck;
import androidx.test.filters.MediumTest;
@@ -43,14 +49,20 @@
public class ScrollViewFunctionalTest {
private ScrollViewActivity mActivity;
private ScrollView mScrollView;
+ private MyScrollView mMyScrollView;
@Rule
public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
ScrollViewActivity.class);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void setUp() throws Exception {
mActivity = mActivityRule.getActivity();
mScrollView = mActivity.findViewById(R.id.scroll_view);
+ mMyScrollView = (MyScrollView) mActivity.findViewById(R.id.my_scroll_view);
}
@Test
@@ -79,6 +91,22 @@
assertEquals(maxScroll, mScrollView.getScrollY());
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+ public void testSetVelocity() throws Throwable {
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.setFrameContentVelocity(0);
+ });
+ // set setFrameContentVelocity shouldn't do anything.
+ assertEquals(mMyScrollView.isSetVelocityCalled, false);
+
+ mActivityRule.runOnUiThread(() -> {
+ mMyScrollView.fling(100);
+ });
+ // set setFrameContentVelocity should be called when fling.
+ assertEquals(mMyScrollView.isSetVelocityCalled, true);
+ }
+
static class WatchedEdgeEffect extends EdgeEffect {
public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
@@ -92,5 +120,29 @@
onAbsorbLatch.countDown();
}
}
+
+ public static class MyScrollView extends ScrollView {
+
+ public boolean isSetVelocityCalled;
+
+ public MyScrollView(Context context) {
+ super(context);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void setFrameContentVelocity(float pixelsPerSecond) {
+ if (pixelsPerSecond != 0) {
+ isSetVelocityCalled = true;
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 58cfc66..43e6227 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -53,6 +53,7 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -93,6 +94,9 @@
private static final String TYPE_PLAIN_TEXT = "text/plain";
private static UserInfo MANAGED_PROFILE_INFO = new UserInfo();
+ private static UserInfo PRIVATE_PROFILE_INFO = new UserInfo(12, "Private", null,
+ UserInfo.FLAG_PROFILE, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
static {
MANAGED_PROFILE_INFO.id = 10;
@@ -131,6 +135,7 @@
@Before
public void setup() {
+
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
sInjector = spy(new TestInjector());
@@ -632,6 +637,54 @@
logMakerCaptor.getValue().getSubtype());
}
+ @Test
+ public void shouldForwardToParent_telephony_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_DIAL);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
+ @Test
+ public void shouldForwardToParent_mms_privateProfile() throws Exception {
+ mSetFlagsRule.enableFlags(
+ android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_INTENT_REDIRECTION);
+
+ sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
+ when(mIPm.canForwardTo(
+ any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true);
+
+ List<UserInfo> profiles = new ArrayList<>();
+ profiles.add(CURRENT_USER_INFO);
+ profiles.add(PRIVATE_PROFILE_INFO);
+ when(mUserManager.getProfiles(anyInt())).thenReturn(profiles);
+ when(mUserManager.getProfileParent(anyInt())).thenReturn(CURRENT_USER_INFO);
+ Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class);
+ intent.setAction(Intent.ACTION_SEND);
+ intent.setType(TYPE_PLAIN_TEXT);
+ IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent);
+ verify(mIPm).canForwardTo(any(), any(), anyInt(), anyInt());
+ assertEquals(activity.getStartActivityIntent().getAction(), intent.getAction());
+ assertEquals(activity.getStartActivityIntent().getType(), intent.getType());
+ assertEquals(activity.getUserIdActivityLaunchedIn(), CURRENT_USER_INFO.id);
+ }
+
private void setupShouldSkipDisclosureTest() throws RemoteException {
sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME;
sActivityName = "MyTestActivity";
@@ -688,6 +741,14 @@
protected MetricsLogger getMetricsLogger() {
return mMetricsLogger;
}
+
+ Intent getStartActivityIntent() {
+ return mStartActivityIntent;
+ }
+
+ int getUserIdActivityLaunchedIn() {
+ return mUserIdActivityLaunchedIn;
+ }
}
public class TestInjector implements IntentForwarderActivity.Injector {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index b209c7c..cb8754a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -1162,7 +1162,8 @@
@Test
public void testTriggerFromPrivateProfile_withoutWorkProfile() throws RemoteException {
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> privateResolvedComponentInfos =
@@ -1183,7 +1184,8 @@
@Test
public void testTriggerFromPrivateProfile_withWorkProfilePresent(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
ResolverActivity.ENABLE_TABBED_VIEW = false;
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
@@ -1205,7 +1207,8 @@
@Test
public void testPrivateProfile_triggerFromPrimaryUser_withWorkProfilePresent(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
@@ -1228,7 +1231,8 @@
@Test
public void testPrivateProfile_triggerFromWorkProfile(){
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ALLOW_RESOLVER_SHEET_FOR_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
markPrivateProfileUserAvailable();
markWorkProfileUserAvailable();
Intent sendIntent = createSendImageIntent();
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66be05f..ed641e0 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -309,17 +309,17 @@
private void pauseActivity(ActivityClientRecord r) {
mThread.handlePauseActivity(r, false /* finished */,
- false /* userLeaving */, 0 /* configChanges */, false /* autoEnteringPip */,
+ false /* userLeaving */, false /* autoEnteringPip */,
null /* pendingActions */, "test");
}
private void stopActivity(ActivityClientRecord r) {
- mThread.handleStopActivity(r, 0 /* configChanges */,
+ mThread.handleStopActivity(r,
new PendingTransactionActions(), false /* finalStateRequest */, "test");
}
private void destroyActivity(ActivityClientRecord r) {
- mThread.handleDestroyActivity(r, true /* finishing */, 0 /* configChanges */,
+ mThread.handleDestroyActivity(r, true /* finishing */,
false /* getNonConfigInstance */, "test");
}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 238a3e1..1410950 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -72,6 +72,12 @@
src: "enhanced-confirmation.xml",
}
+prebuilt_etc {
+ name: "package-shareduid-allowlist.xml",
+ sub_dir: "sysconfig",
+ src: "package-shareduid-allowlist.xml",
+}
+
// Privapp permission whitelist files
prebuilt_etc {
diff --git a/data/etc/CleanSpec.mk b/data/etc/CleanSpec.mk
index 783a7ed..fd38d27 100644
--- a/data/etc/CleanSpec.mk
+++ b/data/etc/CleanSpec.mk
@@ -43,6 +43,8 @@
#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/sysconfig/package-shareduid-allowlist.xml)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/sysconfig/package-shareduid-allowlist.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.carrierconfig.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/etc/permissions/com.android.carrierconfig.xml)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/etc/permissions/com.android.emergency.xml)
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index ce2543a..a621642 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -88,5 +88,6 @@
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
<permission name="android.permission.READ_SEARCH_INDEXABLES" />
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT"/>
+ <permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/package-shareduid-allowlist.xml b/data/etc/package-shareduid-allowlist.xml
new file mode 100644
index 0000000..2401d4a
--- /dev/null
+++ b/data/etc/package-shareduid-allowlist.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+This XML defines an allowlist for packages that want to join a particular shared-uid.
+If a non-system package that is signed with platform signature, is trying to join a particular
+shared-uid, and not in this list, the installation will fail.
+
+- The "package" XML attribute refers to the app's package name.
+- The "shareduid" XML attribute refers to the shared uid name.
+
+Example usage
+ 1. <allow-package-shareduid package="com.example.app" shareduid="android.uid.system"/>
+ Indicates that a package - com.example.app, will be able to join android.uid.system.
+ 2. <allow-package-shareduid package="oem.example.app" shareduid="oem.uid.custom"/>
+ Indicates that a package - oem.example.app, will be able to join oem.uid.custom.
+-->
+
+<config>
+ <allow-package-shareduid package="android.test.settings" shareduid="android.uid.system" />
+</config>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 051e73f..0f12438 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -455,7 +455,7 @@
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
<permission name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <permission name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
diff --git a/data/fonts/font_fallback.xml b/data/fonts/font_fallback.xml
index 15ea15a..53024ab 100644
--- a/data/fonts/font_fallback.xml
+++ b/data/fonts/font_fallback.xml
@@ -792,14 +792,10 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
</family>
<family lang="ko">
<font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Regular">
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
index c1ca67e..ac1b064 100644
--- a/data/fonts/font_fallback_cjkvf.xml
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -804,14 +804,10 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font postScriptName="NotoSerifHentaigana-ExtraLight" supportedAxes="wght">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
- <axis tag="wght" stylevalue="700"/>
- </font>
</family>
<family lang="ko">
<font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index b23f005..d1aa8e9 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -1433,12 +1433,12 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="700"/>
</font>
</family>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
index 1ab71ae..9545ae7 100644
--- a/data/fonts/fonts_cjkvf.xml
+++ b/data/fonts/fonts_cjkvf.xml
@@ -1532,12 +1532,12 @@
</font>
</family>
<family lang="ja">
- <font weight="400" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="400" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="400"/>
</font>
- <font weight="700" style="normal">
- NotoSerifHentaigana-EL.ttf
+ <font weight="700" style="normal" postScriptName="NotoSerifHentaigana-ExtraLight">
+ NotoSerifHentaigana.ttf
<axis tag="wght" stylevalue="700"/>
</font>
</family>
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index b6ce9b6..f9ac02a 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -789,10 +789,8 @@
* @param m The 4x4 matrix to preconcatenate with the current matrix
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void concat44(@Nullable Matrix44 m) {
- if (m != null) {
- nConcat(mNativeCanvasWrapper, m.mBackingArray);
- }
+ public void concat(@Nullable Matrix44 m) {
+ if (m != null) nConcat(mNativeCanvasWrapper, m.mBackingArray);
}
/**
diff --git a/graphics/java/android/graphics/Matrix44.java b/graphics/java/android/graphics/Matrix44.java
index 7cc0eb7..a99e201 100644
--- a/graphics/java/android/graphics/Matrix44.java
+++ b/graphics/java/android/graphics/Matrix44.java
@@ -17,6 +17,7 @@
package android.graphics;
import android.annotation.FlaggedApi;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import com.android.graphics.hwui.flags.Flags;
@@ -98,11 +99,11 @@
/**
* Gets the value at the matrix's row and column.
*
- * @param row An integer from 0 to 4 indicating the row of the value to get
- * @param col An integer from 0 to 4 indicating the column of the value to get
+ * @param row An integer from 0 to 3 indicating the row of the value to get
+ * @param col An integer from 0 to 3 indicating the column of the value to get
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public float get(int row, int col) {
+ public float get(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
return mBackingArray[row * 4 + col];
}
@@ -112,12 +113,13 @@
/**
* Sets the value at the matrix's row and column to the provided value.
*
- * @param row An integer from 0 to 4 indicating the row of the value to change
- * @param col An integer from 0 to 4 indicating the column of the value to change
+ * @param row An integer from 0 to 3 indicating the row of the value to change
+ * @param col An integer from 0 to 3 indicating the column of the value to change
* @param val The value the element at the specified index will be set to
*/
@FlaggedApi(Flags.FLAG_MATRIX_44)
- public void set(int row, int col, float val) {
+ public void set(@IntRange(from = 0, to = 3) int row, @IntRange(from = 0, to = 3) int col,
+ float val) {
if (row >= 0 && row < 4 && col >= 0 && col < 4) {
mBackingArray[row * 4 + col] = val;
} else {
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 6da0719..884268a 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -29,11 +29,13 @@
import com.android.internal.util.Preconditions;
import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.NeverInline;
import libcore.util.NativeAllocationRegistry;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
import java.util.Objects;
/**
@@ -85,6 +87,30 @@
return mChars;
}
+ private void rangeCheck(int start, int end) {
+ if (start < 0 || start > end || end > mChars.length) {
+ throwRangeError(start, end);
+ }
+ }
+
+ @NeverInline
+ private void throwRangeError(int start, int end) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "start(%d) end(%d) length(%d) out of bounds", start, end, mChars.length));
+ }
+
+ private void offsetCheck(int offset) {
+ if (offset < 0 || offset >= mChars.length) {
+ throwOffsetError(offset);
+ }
+ }
+
+ @NeverInline
+ private void throwOffsetError(int offset) {
+ throw new IllegalArgumentException(String.format(Locale.US,
+ "offset (%d) length(%d) out of bounds", offset, mChars.length));
+ }
+
/**
* Returns the width of a given range.
*
@@ -93,12 +119,7 @@
*/
public @FloatRange(from = 0.0) @Px float getWidth(
@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
return nGetWidth(mNativePtr, start, end);
}
@@ -120,12 +141,7 @@
*/
public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Rect rect) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Preconditions.checkNotNull(rect);
nGetBounds(mNativePtr, mChars, start, end, rect);
}
@@ -139,12 +155,7 @@
*/
public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end,
@NonNull Paint.FontMetricsInt outMetrics) {
- Preconditions.checkArgument(0 <= start && start <= mChars.length,
- "start(%d) must be 0 <= start <= %d", start, mChars.length);
- Preconditions.checkArgument(0 <= end && end <= mChars.length,
- "end(%d) must be 0 <= end <= %d", end, mChars.length);
- Preconditions.checkArgument(start <= end,
- "start(%d) is larger than end(%d)", start, end);
+ rangeCheck(start, end);
Objects.requireNonNull(outMetrics);
long packed = nGetExtent(mNativePtr, mChars, start, end);
@@ -160,8 +171,7 @@
* @param offset an offset of the character.
*/
public @FloatRange(from = 0.0f) @Px float getCharWidthAt(@IntRange(from = 0) int offset) {
- Preconditions.checkArgument(0 <= offset && offset < mChars.length,
- "offset(%d) is larger than text length %d" + offset, mChars.length);
+ offsetCheck(offset);
return nGetCharWidthAt(mNativePtr, offset);
}
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 038d008..1abda42 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -56,6 +56,7 @@
import android.app.ActivityThread;
import android.app.Application;
import android.app.Instrumentation;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -103,6 +104,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
/**
* Main controller class that manages split states and presentation.
@@ -178,6 +180,20 @@
private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();
+ /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
+ mEmbeddedActivityWindowInfoCallback;
+
+ /** Listener registered to {@link ClientTransactionListenerController}. */
+ @GuardedBy("mLock")
+ @Nullable
+ private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
+ Flags.activityWindowInfoFlag()
+ ? this::onActivityWindowInfoChanged
+ : null;
+
private final Handler mHandler;
final Object mLock = new Object();
private final ActivityStartMonitor mActivityStartMonitor;
@@ -2456,6 +2472,13 @@
}
@VisibleForTesting
+ @Nullable
+ ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
+ return ActivityThread.currentActivityThread()
+ .getActivityClient(activity.getActivityToken());
+ }
+
+ @VisibleForTesting
ActivityStartMonitor getActivityStartMonitor() {
return mActivityStartMonitor;
}
@@ -2468,8 +2491,7 @@
@VisibleForTesting
@Nullable
IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
- final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
- .getActivityClient(activity.getActivityToken());
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
return record != null ? record.mTaskFragmentToken : null;
}
@@ -2876,17 +2898,102 @@
}
}
+ @Override
+ public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
+ @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ ClientTransactionListenerController.getInstance()
+ .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
+ }
+ }
+
+ @Override
+ public void clearEmbeddedActivityWindowInfoCallback() {
+ if (!Flags.activityWindowInfoFlag()) {
+ return;
+ }
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ mEmbeddedActivityWindowInfoCallback = null;
+ ClientTransactionListenerController.getInstance()
+ .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
+ }
+ }
+
+ @VisibleForTesting
+ @GuardedBy("mLock")
@Nullable
- private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
+ BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
+ return mActivityWindowInfoListener;
+ }
+
+ @Nullable
+ @Override
+ public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
+ if (!Flags.activityWindowInfoFlag()) {
+ return null;
+ }
+ synchronized (mLock) {
+ final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
+ return activityWindowInfo != null
+ ? translateActivityWindowInfo(activity, activityWindowInfo)
+ : null;
+ }
+ }
+
+ @VisibleForTesting
+ void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
+ @NonNull ActivityWindowInfo activityWindowInfo) {
+ synchronized (mLock) {
+ if (mEmbeddedActivityWindowInfoCallback == null) {
+ return;
+ }
+ final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
+ final Consumer<EmbeddedActivityWindowInfo> callback =
+ mEmbeddedActivityWindowInfoCallback.second;
+
+ final Activity activity = getActivity(activityToken);
+ if (activity == null) {
+ return;
+ }
+ final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
+ activity, activityWindowInfo);
+
+ executor.execute(() -> callback.accept(info));
+ }
+ }
+
+ @Nullable
+ private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
if (activity.isFinishing()) {
return null;
}
- final ActivityThread.ActivityClientRecord record =
- ActivityThread.currentActivityThread()
- .getActivityClient(activity.getActivityToken());
+ final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
return record != null ? record.getActivityWindowInfo() : null;
}
+ @NonNull
+ private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
+ @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
+ final boolean isEmbedded = activityWindowInfo.isEmbedded();
+ final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
+ .windowConfiguration.getBounds());
+ final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
+ final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
+ return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
+ activityStackBounds);
+ }
+
/**
* 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
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 00f8b59..bdeeb73 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
@@ -72,6 +72,8 @@
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.ActivityWindowInfo;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
+import com.android.window.flags.Flags;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -110,6 +117,8 @@
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -127,6 +136,9 @@
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
new ComponentName("test", "placeholder"));
+ @Rule
+ public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
private Activity mActivity;
@Mock
private Resources mActivityResources;
@@ -138,6 +150,13 @@
private Handler mHandler;
@Mock
private WindowLayoutComponentImpl mWindowLayoutComponent;
+ @Mock
+ private ActivityWindowInfo mActivityWindowInfo;
+ @Mock
+ private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
+ @Mock
+ private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
+ mEmbeddedActivityWindowInfoCallback;
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
@@ -1529,6 +1548,73 @@
.getTopNonFinishingActivity(), secondaryActivity);
}
+ @Test
+ public void testIsActivityEmbedded() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ assertFalse(mSplitController.isActivityEmbedded(mActivity));
+
+ doReturn(true).when(mActivityWindowInfo).isEmbedded();
+
+ assertTrue(mSplitController.isActivityEmbedded(mActivity));
+ }
+
+ @Test
+ public void testGetEmbeddedActivityWindowInfo() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final boolean isEmbedded = true;
+ final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
+ .getBounds();
+ final Rect taskBounds = new Rect(0, 0, 1000, 2000);
+ final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
+ doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
+ doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
+ doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();
+
+ final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
+ isEmbedded, activityBounds, taskBounds, activityStackBounds);
+ assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
+ }
+
+ @Test
+ public void testSetEmbeddedActivityWindowInfoCallback() {
+ mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);
+
+ final ClientTransactionListenerController controller = ClientTransactionListenerController
+ .getInstance();
+ spyOn(controller);
+ doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
+ doReturn(mActivityWindowInfoListener).when(mSplitController)
+ .getActivityWindowInfoListener();
+ final Executor executor = Runnable::run;
+
+ // Register to ClientTransactionListenerController
+ mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
+ mEmbeddedActivityWindowInfoCallback);
+
+ verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+
+ // Test onActivityWindowInfoChanged triggered.
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback).accept(any());
+
+ // Unregister to ClientTransactionListenerController
+ mSplitController.clearEmbeddedActivityWindowInfoCallback();
+
+ verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
+
+ // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
+ clearInvocations(mEmbeddedActivityWindowInfoCallback);
+ mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
+ mActivityWindowInfo);
+
+ verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity(int taskId) {
final Activity activity = mock(Activity.class);
+ final ActivityThread.ActivityClientRecord activityClientRecord =
+ mock(ActivityThread.ActivityClientRecord.class);
doReturn(mActivityResources).when(activity).getResources();
final IBinder activityToken = new Binder();
doReturn(activityToken).when(activity).getActivityToken();
doReturn(activity).when(mSplitController).getActivity(activityToken);
+ doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
doReturn(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+ doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
return activity;
}
diff --git a/libs/WindowManager/Shell/res/drawable/circular_progress.xml b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
index 9482645..294b1f0 100644
--- a/libs/WindowManager/Shell/res/drawable/circular_progress.xml
+++ b/libs/WindowManager/Shell/res/drawable/circular_progress.xml
@@ -25,7 +25,7 @@
<shape
android:shape="ring"
android:thickness="3dp"
- android:innerRadius="17dp"
+ android:innerRadius="14dp"
android:useLevel="true">
</shape>
</rotate>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
index 02b7075..e5fe1b54 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_desktop_mode_maximize_button_dark.xml
@@ -15,12 +15,12 @@
~ limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="48dp"
- android:height="48dp"
+ android:width="24dp"
+ android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="960"
android:viewportWidth="960">
<path
- android:fillColor="@android:color/white"
- android:pathData="M180,840Q156,840 138,822Q120,804 120,780L120,180Q120,156 138,138Q156,120 180,120L780,120Q804,120 822,138Q840,156 840,180L840,780Q840,804 822,822Q804,840 780,840L180,840ZM180,780L780,780Q780,780 780,780Q780,780 780,780L780,277L180,277L180,780Q180,780 180,780Q180,780 180,780Z" />
+ android:fillColor="@android:color/black"
+ android:pathData="M160,800Q127,800 103.5,776.5Q80,753 80,720L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,320L160,320L160,720Q160,720 160,720Q160,720 160,720Z"/>
</vector>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
deleted file mode 100644
index 7c4f499..0000000
--- a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_select.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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.
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="20dp"
- android:height="20dp"
- android:viewportWidth="20"
- android:viewportHeight="20">
- <path
- android:pathData="M15.701,14.583L18.567,17.5L17.425,18.733L14.525,15.833L12.442,17.917V12.5H17.917L15.701,14.583ZM15.833,5.833H17.5V7.5H15.833V5.833ZM17.5,4.167H15.833V2.567C16.75,2.567 17.5,3.333 17.5,4.167ZM12.5,2.5H14.167V4.167H12.5V2.5ZM15.833,9.167H17.5V10.833H15.833V9.167ZM7.5,17.5H5.833V15.833H7.5V17.5ZM4.167,7.5H2.5V5.833H4.167V7.5ZM4.167,2.567V4.167H2.5C2.5,3.333 3.333,2.567 4.167,2.567ZM4.167,14.167H2.5V12.5H4.167V14.167ZM7.5,4.167H5.833V2.5H7.5V4.167ZM10.833,4.167H9.167V2.5H10.833V4.167ZM10.833,17.5H9.167V15.833H10.833V17.5ZM4.167,10.833H2.5V9.167H4.167V10.833ZM4.167,17.567C3.25,17.567 2.5,16.667 2.5,15.833H4.167V17.567Z"
- android:fillColor="#1C1C14"/>
-</vector>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
index a5605a7..fa18e2b 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_app_controls_window_decor.xml
@@ -80,11 +80,14 @@
<com.android.wm.shell.windowdecor.MaximizeButtonView
android:id="@+id/maximize_button_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="44dp"
+ android:layout_height="40dp"
android:layout_gravity="end"
+ android:layout_marginHorizontal="8dp"
+ android:paddingHorizontal="5dp"
+ android:paddingVertical="3dp"
android:clickable="true"
- android:focusable="true" />
+ android:focusable="true"/>
<ImageButton
android:id="@+id/close_window"
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index c6f85a0..fca2fe4 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -134,15 +134,6 @@
android:drawableStart="@drawable/desktop_mode_ic_handle_menu_screenshot"
android:drawableTint="?androidprv:attr/materialColorOnSurface"
style="@style/DesktopModeHandleMenuActionButton"/>
-
- <Button
- android:id="@+id/select_button"
- android:contentDescription="@string/select_text"
- android:text="@string/select_text"
- android:drawableStart="@drawable/desktop_mode_ic_handle_menu_select"
- android:drawableTint="?androidprv:attr/materialColorOnSurface"
- style="@style/DesktopModeHandleMenuActionButton"/>
-
</LinearLayout>
</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
index e0057fe..296c8956 100644
--- a/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
+++ b/libs/WindowManager/Shell/res/layout/maximize_menu_button.xml
@@ -20,16 +20,16 @@
android:id="@+id/progress_bar"
style="?android:attr/progressBarStyleHorizontal"
android:progressDrawable="@drawable/circular_progress"
- android:layout_width="40dp"
- android:layout_height="40dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
android:indeterminate="false"
android:visibility="invisible"/>
<ImageButton
android:id="@+id/maximize_window"
- android:layout_width="40dp"
- android:layout_height="40dp"
- android:padding="9dp"
+ android:layout_width="34dp"
+ android:layout_height="34dp"
+ android:padding="5dp"
android:contentDescription="@string/maximize_button_text"
android:tint="?androidprv:attr/materialColorOnSurface"
android:background="?android:selectableItemBackgroundBorderless"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 8baaf2f..a541c59 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -145,4 +145,7 @@
<!-- Whether CompatUIController is enabled -->
<bool name="config_enableCompatUIController">true</bool>
+
+ <!-- Whether pointer pilfer is required to start back animation. -->
+ <bool name="config_backAnimationRequiresPointerPilfer">true</bool>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 74967ef0..7dd3961 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -510,10 +510,6 @@
split select if dragged until the touch input is within the range. -->
<dimen name="desktop_mode_transition_area_width">32dp</dimen>
- <!-- The height of the area at the top of the screen where a freeform task will transition to
- fullscreen if dragged until the top bound of the task is within the area. -->
- <dimen name="desktop_mode_transition_area_height">16dp</dimen>
-
<!-- The width of the area where a desktop task will transition to fullscreen. -->
<dimen name="desktop_mode_fullscreen_from_desktop_width">80dp</dimen>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 2606fb6..9bd8531 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -64,6 +64,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
+import com.android.wm.shell.R;
import com.android.wm.shell.animation.FlingAnimationUtils;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -115,6 +116,7 @@
private boolean mShouldStartOnNextMoveEvent = false;
private boolean mOnBackStartDispatched = false;
private boolean mPointerPilfered = false;
+ private final boolean mRequirePointerPilfer;
private final FlingAnimationUtils mFlingAnimationUtils;
@@ -220,6 +222,8 @@
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
+ mRequirePointerPilfer =
+ context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mAnimationBackground = backAnimationBackground;
@@ -560,7 +564,9 @@
private void tryDispatchOnBackStarted(
IOnBackInvokedCallback callback,
BackMotionEvent backEvent) {
- if (mOnBackStartDispatched || callback == null || !mPointerPilfered) {
+ if (mOnBackStartDispatched
+ || callback == null
+ || (!mPointerPilfered && mRequirePointerPilfer)) {
return;
}
dispatchOnBackStarted(callback, backEvent);
@@ -1006,6 +1012,8 @@
pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
+ pw.println(prefix + " mPointerPilfered=" + mPointerPilfered);
+ pw.println(prefix + " mRequirePointerPilfer=" + mRequirePointerPilfer);
pw.println(prefix + " mCurrentTracker state:");
mCurrentTracker.dump(pw, prefix + " ");
pw.println(prefix + " mQueuedTracker state:");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 8fd6ffe..474430e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -717,11 +717,6 @@
// Hide the stack after a delay, if needed.
updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
-
- if (mShouldReorderBubblesAfterGestureCompletes) {
- mShouldReorderBubblesAfterGestureCompletes = false;
- updateBubbleOrderInternal(mBubbleData.getBubbles(), true);
- }
}
};
@@ -2732,6 +2727,12 @@
ev.getAction() != MotionEvent.ACTION_UP
&& ev.getAction() != MotionEvent.ACTION_CANCEL;
+ // If there is a deferred reorder action, and the gesture is over, run it now.
+ if (mShouldReorderBubblesAfterGestureCompletes && !mIsGestureInProgress) {
+ mShouldReorderBubblesAfterGestureCompletes = false;
+ updateBubbleOrderInternal(mBubbleData.getBubbles(), false);
+ }
+
return dispatched;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 22ba708..7b84868 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -16,6 +16,10 @@
package com.android.wm.shell.desktopmode;
+import static android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.SystemProperties;
import com.android.window.flags.Flags;
@@ -66,6 +70,9 @@
private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
"persist.wm.debug.desktop_use_rounded_corners", true);
+ private static final boolean ENFORCE_DISPLAY_RESTRICTIONS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_enforce_display_restrictions", true);
+
/**
* Return {@code true} if desktop windowing is enabled
*/
@@ -104,4 +111,21 @@
public static boolean useRoundedCorners() {
return USE_ROUNDED_CORNERS;
}
+
+ /**
+ * Return whether the display size restrictions should be enforced.
+ */
+ public static boolean enforceDisplayRestrictions() {
+ return ENFORCE_DISPLAY_RESTRICTIONS;
+ }
+
+ /**
+ * Return {@code true} if the display associated with the task is at least of size
+ * {@link android.content.res.Configuration#SCREENLAYOUT_SIZE_XLARGE} or has been overridden to
+ * ignore the size constraint.
+ */
+ public static boolean meetsMinimumDisplayRequirements(@NonNull RunningTaskInfo taskInfo) {
+ return !enforceDisplayRestrictions()
+ || taskInfo.configuration.isLayoutSizeAtLeast(SCREENLAYOUT_SIZE_XLARGE);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 7091c4b..fb0ed15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -98,6 +98,7 @@
* Based on the coordinates of the current drag event, determine which indicator type we should
* display, including no visible indicator.
*/
+ @NonNull
IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
// If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
@@ -136,18 +137,18 @@
Region calculateFullscreenRegion(DisplayLayout layout,
@WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
final Region region = new Region();
- int edgeTransitionHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height);
+ int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+ ? 2 * layout.stableInsets().top
+ : mContext.getResources().getDimensionPixelSize(
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
// A thin, short Rect at the top of the screen.
if (windowingMode == WINDOWING_MODE_FREEFORM) {
int fromFreeformWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_width);
- int fromFreeformHeight = mContext.getResources().getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height);
region.union(new Rect((layout.width() / 2) - (fromFreeformWidth / 2),
-captionHeight,
(layout.width() / 2) + (fromFreeformWidth / 2),
- fromFreeformHeight));
+ transitionHeight));
}
// A screen-wide, shorter Rect if the task is in fullscreen or split.
if (windowingMode == WINDOWING_MODE_FULLSCREEN
@@ -155,7 +156,7 @@
region.union(new Rect(0,
-captionHeight,
layout.width(),
- edgeTransitionHeight));
+ transitionHeight));
}
return region;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index d1328ca..2c66fd6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -140,7 +140,7 @@
private val transitionAreaHeight
get() = context.resources.getDimensionPixelSize(
- com.android.wm.shell.R.dimen.desktop_mode_transition_area_height
+ com.android.wm.shell.R.dimen.desktop_mode_fullscreen_from_desktop_height
)
private val transitionAreaWidth
@@ -305,6 +305,12 @@
task: RunningTaskInfo,
wct: WindowContainerTransaction = WindowContainerTransaction()
) {
+ if (!DesktopModeStatus.meetsMinimumDisplayRequirements(task)) {
+ KtProtoLog.w(
+ WM_SHELL_DESKTOP_MODE, "DesktopTasksController: Cannot enter desktop, " +
+ "display does not meet minimum size requirements")
+ return
+ }
KtProtoLog.v(
WM_SHELL_DESKTOP_MODE,
"DesktopTasksController: moveToDesktop taskId=%d",
@@ -565,30 +571,7 @@
* @param position the portion of the screen (RIGHT or LEFT) we want to snap the task to.
*/
fun snapToHalfScreen(taskInfo: RunningTaskInfo, position: SnapPosition) {
- val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return
-
- val stableBounds = Rect()
- displayLayout.getStableBounds(stableBounds)
-
- val destinationWidth = stableBounds.width() / 2
- val destinationBounds = when (position) {
- SnapPosition.LEFT -> {
- Rect(
- stableBounds.left,
- stableBounds.top,
- stableBounds.left + destinationWidth,
- stableBounds.bottom
- )
- }
- SnapPosition.RIGHT -> {
- Rect(
- stableBounds.right - destinationWidth,
- stableBounds.top,
- stableBounds.right,
- stableBounds.bottom
- )
- }
- }
+ val destinationBounds = getSnapBounds(taskInfo, position)
if (destinationBounds == taskInfo.configuration.windowConfiguration.bounds) return
@@ -609,8 +592,35 @@
outBounds.set(0, 0, desiredWidth, desiredHeight)
// Center the task in screen bounds
outBounds.offset(
- screenBounds.centerX() - outBounds.centerX(),
- screenBounds.centerY() - outBounds.centerY())
+ screenBounds.centerX() - outBounds.centerX(),
+ screenBounds.centerY() - outBounds.centerY())
+ }
+
+ private fun getSnapBounds(taskInfo: RunningTaskInfo, position: SnapPosition): Rect {
+ val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return Rect()
+
+ val stableBounds = Rect()
+ displayLayout.getStableBounds(stableBounds)
+
+ val destinationWidth = stableBounds.width() / 2
+ return when (position) {
+ SnapPosition.LEFT -> {
+ Rect(
+ stableBounds.left,
+ stableBounds.top,
+ stableBounds.left + destinationWidth,
+ stableBounds.bottom
+ )
+ }
+ SnapPosition.RIGHT -> {
+ Rect(
+ stableBounds.right - destinationWidth,
+ stableBounds.top,
+ stableBounds.right,
+ stableBounds.bottom
+ )
+ }
+ }
}
/**
@@ -646,7 +656,7 @@
?.let { homeTask -> wct.reorder(homeTask.getToken(), true /* onTop */) }
}
- private fun releaseVisualIndicator() {
+ fun releaseVisualIndicator() {
val t = SurfaceControl.Transaction()
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
@@ -927,16 +937,13 @@
taskSurface: SurfaceControl,
inputX: Float,
taskTop: Float
- ) {
+ ): DesktopModeVisualIndicator.IndicatorType {
// If the visual indicator does not exist, create it.
- if (visualIndicator == null) {
- visualIndicator = DesktopModeVisualIndicator(
- syncQueue, taskInfo, displayController, context, taskSurface,
- rootTaskDisplayAreaOrganizer)
- }
- // Then, update the indicator type.
- val indicator = visualIndicator ?: return
- indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
+ val indicator = visualIndicator ?: DesktopModeVisualIndicator(
+ syncQueue, taskInfo, displayController, context, taskSurface,
+ rootTaskDisplayAreaOrganizer)
+ if (visualIndicator == null) visualIndicator = indicator
+ return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
}
/**
@@ -956,20 +963,28 @@
if (taskInfo.configuration.windowConfiguration.windowingMode != WINDOWING_MODE_FREEFORM) {
return
}
- if (taskBounds.top <= transitionAreaHeight) {
- moveToFullscreenWithAnimation(taskInfo, position)
- return
- }
- if (inputCoordinate.x <= transitionAreaWidth) {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.LEFT)
- return
- }
- if (inputCoordinate.x >= (displayController.getDisplayLayout(taskInfo.displayId)?.width()
- ?.minus(transitionAreaWidth) ?: return)) {
- releaseVisualIndicator()
- snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
- return
+
+ val indicator = visualIndicator ?: return
+ val indicatorType = indicator.updateIndicatorType(
+ PointF(inputCoordinate.x, taskBounds.top.toFloat()),
+ taskInfo.windowingMode
+ )
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ moveToFullscreenWithAnimation(taskInfo, position)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.LEFT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ releaseVisualIndicator()
+ snapToHalfScreen(taskInfo, SnapPosition.RIGHT)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR -> {
+ releaseVisualIndicator()
+ }
}
// A freeform drag-move ended, remove the indicator immediately.
releaseVisualIndicator()
@@ -982,14 +997,28 @@
* @param y height of drag, to be checked against status bar height.
*/
fun onDragPositioningEndThroughStatusBar(
+ inputCoordinates: PointF,
taskInfo: RunningTaskInfo,
freeformBounds: Rect
) {
- finalizeDragToDesktop(taskInfo, freeformBounds)
- }
-
- private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int {
- return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
+ val indicator = visualIndicator ?: return
+ val indicatorType = indicator
+ .updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+ when (indicatorType) {
+ DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, freeformBounds)
+ }
+ DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR,
+ DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ cancelDragToDesktop(taskInfo)
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT))
+ }
+ DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+ finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT))
+ }
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 2cdec81..139cde2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -122,6 +122,8 @@
private static final long PIP_KEEP_CLEAR_AREAS_DELAY =
SystemProperties.getLong("persist.wm.debug.pip_keep_clear_areas_delay", 200);
+ private static final long ENABLE_TOUCH_DELAY_MS = 200L;
+
private Context mContext;
protected ShellExecutor mMainExecutor;
private DisplayController mDisplayController;
@@ -152,6 +154,8 @@
private final Runnable mMovePipInResponseToKeepClearAreasChangeCallback =
this::onKeepClearAreasChangedCallback;
+ private final Runnable mEnableTouchCallback = () -> mTouchHandler.setTouchEnabled(true);
+
private void onKeepClearAreasChangedCallback() {
if (mIsKeyguardShowingOrAnimating) {
// early bail out if the change was caused by keyguard showing up
@@ -979,7 +983,13 @@
// cache current min/max size
Point minSize = mPipBoundsState.getMinSize();
Point maxSize = mPipBoundsState.getMaxSize();
- mPipBoundsState.updateMinMaxSize(pictureInPictureParams.getAspectRatioFloat());
+ final float aspectRatioFloat;
+ if (pictureInPictureParams.hasSetAspectRatio()) {
+ aspectRatioFloat = pictureInPictureParams.getAspectRatioFloat();
+ } else {
+ aspectRatioFloat = mPipBoundsAlgorithm.getDefaultAspectRatio();
+ }
+ mPipBoundsState.updateMinMaxSize(aspectRatioFloat);
final Rect entryBounds = mPipTaskOrganizer.startSwipePipToHome(componentName, activityInfo,
pictureInPictureParams);
// restore min/max size, as this is referenced later in OnDisplayChangingListener and needs
@@ -1037,6 +1047,7 @@
saveReentryState(pipBounds);
}
// Disable touches while the animation is running
+ mMainExecutor.removeCallbacks(mEnableTouchCallback);
mTouchHandler.setTouchEnabled(false);
if (mPinnedStackAnimationRecentsCallback != null) {
mPinnedStackAnimationRecentsCallback.onPipAnimationStarted();
@@ -1067,7 +1078,7 @@
InteractionJankMonitor.getInstance().end(CUJ_PIP_TRANSITION);
// Re-enable touches after the animation completes
- mTouchHandler.setTouchEnabled(true);
+ mMainExecutor.executeDelayed(mEnableTouchCallback, ENABLE_TOUCH_DELAY_MS);
mTouchHandler.onPinnedStackAnimationEnded(direction);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index fa3d8a6..bf22193 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -30,6 +30,10 @@
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
+import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FREEFORM_ANIMATION_DURATION;
import static com.android.wm.shell.windowdecor.MoveToDesktopAnimator.DRAG_FREEFORM_SCALE;
@@ -81,6 +85,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
import com.android.wm.shell.desktopmode.DesktopTasksController;
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
@@ -119,9 +124,8 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
- private final Optional<DesktopTasksController> mDesktopTasksController;
+ private final DesktopTasksController mDesktopTasksController;
private final InputManager mInputManager;
-
private boolean mTransitionDragActive;
private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -129,8 +133,7 @@
private final ExclusionRegionListener mExclusionRegionListener =
new ExclusionRegionListenerImpl();
- private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
- new SparseArray<>();
+ private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
@@ -197,7 +200,8 @@
new DesktopModeWindowDecoration.Factory(),
new InputMonitorFactory(),
SurfaceControl.Transaction::new,
- rootTaskDisplayAreaOrganizer);
+ rootTaskDisplayAreaOrganizer,
+ new SparseArray<>());
}
@VisibleForTesting
@@ -219,7 +223,8 @@
DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
InputMonitorFactory inputMonitorFactory,
Supplier<SurfaceControl.Transaction> transactionFactory,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -231,7 +236,7 @@
mDisplayInsetsController = displayInsetsController;
mSyncQueue = syncQueue;
mTransitions = transitions;
- mDesktopTasksController = desktopTasksController;
+ mDesktopTasksController = desktopTasksController.get();
mShellCommandHandler = shellCommandHandler;
mWindowManager = windowManager;
mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
@@ -239,6 +244,7 @@
mTransactionFactory = transactionFactory;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mInputManager = mContext.getSystemService(InputManager.class);
+ mWindowDecorByTaskId = windowDecorByTaskId;
shellInit.addInitCallback(this::onInit, this);
}
@@ -248,8 +254,8 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(),
new DesktopModeOnInsetsChangedListener());
- mDesktopTasksController.ifPresent(c -> c.setOnTaskResizeAnimationListener(
- new DeskopModeOnTaskResizeAnimationListener()));
+ mDesktopTasksController.setOnTaskResizeAnimationListener(
+ new DeskopModeOnTaskResizeAnimationListener());
try {
mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
mContext.getDisplayId());
@@ -273,7 +279,7 @@
DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
if (decor != null && DesktopModeStatus.isEnabled()
&& decor.mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
- mDesktopTasksController.ifPresent(c -> c.moveToSplit(decor.mTaskInfo));
+ mDesktopTasksController.moveToSplit(decor.mTaskInfo);
}
}
}
@@ -340,8 +346,7 @@
@Override
public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
- final DesktopModeWindowDecoration decoration =
- mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
decoration.close();
@@ -349,6 +354,10 @@
if (mEventReceiversByDisplay.contains(displayId)) {
removeTaskFromEventReceiver(displayId);
}
+ // Remove the decoration from the cache last because WindowDecoration#close could still
+ // issue CANCEL MotionEvents to touch listeners before its view host is released.
+ // See b/327664694.
+ mWindowDecorByTaskId.remove(taskInfo.taskId);
}
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
@@ -414,13 +423,11 @@
decoration.closeHandleMenu();
}
} else if (id == R.id.desktop_button) {
- if (mDesktopTasksController.isPresent()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // App sometimes draws before the insets from WindowDecoration#relayout have
- // been added, so they must be added here
- mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
- mDesktopTasksController.get().moveToDesktop(mTaskId, wct);
- }
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // App sometimes draws before the insets from WindowDecoration#relayout have
+ // been added, so they must be added here
+ mWindowDecorByTaskId.get(mTaskId).addCaptionInset(wct);
+ mDesktopTasksController.moveToDesktop(mTaskId, wct);
decoration.closeHandleMenu();
} else if (id == R.id.fullscreen_button) {
decoration.closeHandleMenu();
@@ -428,42 +435,31 @@
mSplitScreenController.moveTaskToFullscreen(mTaskId,
SplitScreenController.EXIT_REASON_DESKTOP_MODE);
} else {
- mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(mTaskId));
+ mDesktopTasksController.moveToFullscreen(mTaskId);
}
} else if (id == R.id.split_screen_button) {
decoration.closeHandleMenu();
- mDesktopTasksController.ifPresent(c -> {
- c.requestSplit(decoration.mTaskInfo);
- });
+ mDesktopTasksController.requestSplit(decoration.mTaskInfo);
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
- } else if (id == R.id.select_button) {
- if (DesktopModeStatus.IS_DISPLAY_CHANGE_ENABLED) {
- // TODO(b/278084491): dev option to enable display switching
- // remove when select is implemented
- mDesktopTasksController.ifPresent(c -> c.moveToNextDisplay(mTaskId));
- }
} else if (id == R.id.maximize_window) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
} else if (id == R.id.maximize_menu_maximize_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.toggleDesktopTaskSize(taskInfo));
+ mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_left_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, SnapPosition.LEFT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
} else if (id == R.id.maximize_menu_snap_right_button) {
final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.ifPresent(c -> c.snapToHalfScreen(
- taskInfo, SnapPosition.RIGHT));
+ mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
decoration.closeHandleMenu();
decoration.closeMaximizeMenu();
}
@@ -572,7 +568,7 @@
private void moveTaskToFront(RunningTaskInfo taskInfo) {
if (!taskInfo.isFocused) {
- mDesktopTasksController.ifPresent(c -> c.moveTaskToFront(taskInfo));
+ mDesktopTasksController.moveTaskToFront(taskInfo);
}
}
@@ -616,10 +612,10 @@
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningMove(taskInfo,
+ mDesktopTasksController.onDragPositioningMove(taskInfo,
decoration.mTaskSurface,
e.getRawX(dragPointerIdx),
- newTaskBounds));
+ newTaskBounds);
mIsDragging = true;
return true;
}
@@ -641,10 +637,9 @@
(int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx)));
final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
- mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position,
+ mDesktopTasksController.onDragPositioningEnd(taskInfo, position,
new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
- newTaskBounds));
+ newTaskBounds);
if (touchingButton && !mHasLongClicked) {
// We need the input event to not be consumed here to end the ripple
// effect on the touched button. We will reset drag state in the ensuing
@@ -672,10 +667,8 @@
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
- mDesktopTasksController.ifPresent(c -> {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- c.toggleDesktopTaskSize(decoration.mTaskInfo);
- });
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
return true;
}
}
@@ -839,20 +832,29 @@
return;
}
if (mTransitionDragActive) {
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
mTransitionDragActive = false;
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getRawY() > 2 * statusBarHeight) {
+ if (indicatorType == TO_DESKTOP_INDICATOR
+ || indicatorType == TO_SPLIT_LEFT_INDICATOR
+ || indicatorType == TO_SPLIT_RIGHT_INDICATOR) {
if (DesktopModeStatus.isEnabled()) {
animateToDesktop(relevantDecor, ev);
}
mMoveToDesktopAnimator = null;
return;
} else if (mMoveToDesktopAnimator != null) {
- mDesktopTasksController.ifPresent(
- c -> c.cancelDragToDesktop(relevantDecor.mTaskInfo));
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE));
mMoveToDesktopAnimator = null;
return;
+ } else {
+ // In cases where we create an indicator but do not start the
+ // move-to-desktop animation, we need to dismiss it.
+ mDesktopTasksController.releaseVisualIndicator();
}
}
relevantDecor.checkClickEvent(ev);
@@ -864,20 +866,17 @@
return;
}
if (mTransitionDragActive) {
- mDesktopTasksController.ifPresent(
- c -> c.updateVisualIndicator(
+ final DesktopModeVisualIndicator.IndicatorType indicatorType =
+ mDesktopTasksController.updateVisualIndicator(
relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY()));
- final int statusBarHeight = getStatusBarHeight(
- relevantDecor.mTaskInfo.displayId);
- if (ev.getRawY() > statusBarHeight) {
+ relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+ if (indicatorType != TO_FULLSCREEN_INDICATOR) {
if (mMoveToDesktopAnimator == null) {
mMoveToDesktopAnimator = new MoveToDesktopAnimator(
mContext, mDragToDesktopAnimationStartBounds,
relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
- mDesktopTasksController.ifPresent(
- c -> c.startDragToDesktop(relevantDecor.mTaskInfo,
- mMoveToDesktopAnimator));
+ mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
+ mMoveToDesktopAnimator);
}
}
if (mMoveToDesktopAnimator != null) {
@@ -923,6 +922,8 @@
* Animates a window to the center, grows to freeform size, and transitions to Desktop Mode.
* @param relevantDecor the window decor of the task to be animated
* @param ev the motion event that triggers the animation
+ * TODO(b/315527000): This animation needs to be adjusted to allow snap left/right cases.
+ * Currently fullscreen -> split snap still animates to center screen before readjusting.
*/
private void centerAndMoveToDesktopWithAnimation(DesktopModeWindowDecoration relevantDecor,
MotionEvent ev) {
@@ -946,13 +947,12 @@
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mDesktopTasksController.ifPresent(
- c -> {
- c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo,
- calculateFreeformBounds(ev.getDisplayId(),
- DesktopTasksController
- .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
- });
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ calculateFreeformBounds(ev.getDisplayId(),
+ DesktopTasksController
+ .DESKTOP_MODE_INITIAL_BOUNDS_SCALE));
}
});
animator.start();
@@ -1052,8 +1052,7 @@
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop()
- && mDisplayController.getDisplayContext(taskInfo.displayId)
- .getResources().getConfiguration().smallestScreenWidthDp >= 600;
+ && DesktopModeStatus.meetsMinimumDisplayRequirements(taskInfo);
}
private void createWindowDecoration(
@@ -1082,7 +1081,7 @@
final DragPositioningCallback dragPositioningCallback;
final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height);
+ R.dimen.desktop_mode_fullscreen_from_desktop_height);
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
dragPositioningCallback = new FluidResizeTaskPositioner(
mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
@@ -1181,12 +1180,12 @@
@Override
public void onExclusionRegionChanged(int taskId, Region region) {
- mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
+ mDesktopTasksController.onExclusionRegionChanged(taskId, region);
}
@Override
public void onExclusionRegionDismissed(int taskId) {
- mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
+ mDesktopTasksController.removeExclusionRegionForTask(taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index b37dd0d..3d0dd31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -35,7 +35,6 @@
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.View;
-import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -53,6 +52,7 @@
*/
class HandleMenu {
private static final String TAG = "HandleMenu";
+ private static final boolean SHOULD_SHOW_MORE_ACTIONS_PILL = false;
private final Context mContext;
private final WindowDecoration mParentDecor;
private WindowDecoration.AdditionalWindow mHandleMenuWindow;
@@ -185,11 +185,9 @@
* Set up interactive elements & height of handle menu's more actions pill
*/
private void setupMoreActionsPill(View handleMenu) {
- final Button selectBtn = handleMenu.findViewById(R.id.select_button);
- selectBtn.setOnClickListener(mOnClickListener);
- final Button screenshotBtn = handleMenu.findViewById(R.id.screenshot_button);
- // TODO: Remove once implemented.
- screenshotBtn.setVisibility(View.GONE);
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ handleMenu.findViewById(R.id.more_actions_pill).setVisibility(View.GONE);
+ }
}
/**
@@ -305,12 +303,15 @@
* Determines handle menu height based on if windowing pill should be shown.
*/
private int getHandleMenuHeight(Resources resources) {
- int menuHeight = loadDimensionPixelSize(resources,
- R.dimen.desktop_mode_handle_menu_height);
+ int menuHeight = loadDimensionPixelSize(resources, R.dimen.desktop_mode_handle_menu_height);
if (!mShouldShowWindowingPill) {
menuHeight -= loadDimensionPixelSize(resources,
R.dimen.desktop_mode_handle_menu_windowing_pill_height);
}
+ if (!SHOULD_SHOW_MORE_ACTIONS_PILL) {
+ menuHeight -= loadDimensionPixelSize(resources,
+ R.dimen.desktop_mode_handle_menu_more_actions_pill_height);
+ }
return menuHeight;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index f8ce4ee..9703dce 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -56,18 +56,16 @@
context, taskSurface, taskDisplayAreaOrganizer)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
+ whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
}
@Test
fun testFullscreenRegionCalculation() {
val transitionHeight = context.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height)
+ R.dimen.desktop_mode_fullscreen_from_desktop_height)
val fromFreeformWidth = mContext.resources.getDimensionPixelSize(
R.dimen.desktop_mode_fullscreen_from_desktop_width
)
- val fromFreeformHeight = mContext.resources.getDimensionPixelSize(
- R.dimen.desktop_mode_fullscreen_from_desktop_height
- )
var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -77,7 +75,7 @@
DISPLAY_BOUNDS.width() / 2 - fromFreeformWidth / 2,
-50,
DISPLAY_BOUNDS.width() / 2 + fromFreeformWidth / 2,
- fromFreeformHeight))
+ 2 * STABLE_INSETS.top))
testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
@@ -135,5 +133,12 @@
private const val TRANSITION_AREA_WIDTH = 32
private const val CAPTION_HEIGHT = 50
private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private const val NAVBAR_HEIGHT = 50
+ private val STABLE_INSETS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
}
}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 35c803b..4c8a308 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -23,6 +23,8 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE
import android.os.Binder
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
@@ -123,7 +125,7 @@
@Before
fun setUp() {
- mockitoSession = mockitoSession().mockStatic(DesktopModeStatus::class.java).startMocking()
+ mockitoSession = mockitoSession().spyStatic(DesktopModeStatus::class.java).startMocking()
whenever(DesktopModeStatus.isEnabled()).thenReturn(true)
shellInit = Mockito.spy(ShellInit(testExecutor))
@@ -332,6 +334,45 @@
}
@Test
+ fun moveToDesktop_screenSizeBelowXLarge_doesNothing() {
+ val task = setUpFullscreenTask()
+
+ // Update screen layout to be below minimum size
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+ controller.moveToDesktop(task)
+ verifyWCTNotExecuted()
+ }
+
+ @Test
+ fun moveToDesktop_screenSizeBelowXLarge_displayRestrictionsOverridden_taskIsMovedToDesktop() {
+ val task = setUpFullscreenTask()
+
+ // Update screen layout to be below minimum size
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+ // Simulate enforce display restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false)
+
+ controller.moveToDesktop(task)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun moveToDesktop_screenSizeXLarge_taskIsMovedToDesktop() {
+ val task = setUpFullscreenTask()
+
+ controller.moveToDesktop(task)
+
+ val wct = getLatestMoveToDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveToDesktop_otherFreeformTasksBroughtToFront() {
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
@@ -816,6 +857,7 @@
private fun setUpFullscreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFullscreenTask(displayId)
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
return task
@@ -823,6 +865,7 @@
private fun setUpSplitScreenTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createSplitScreenTask(displayId)
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
runningTasks.add(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 917fd71..83519bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -23,12 +23,17 @@
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.Context
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_NORMAL
+import android.content.res.Configuration.SCREENLAYOUT_SIZE_XLARGE
import android.graphics.Rect
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Handler
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.util.SparseArray
import android.view.Choreographer
import android.view.Display.DEFAULT_DISPLAY
import android.view.IWindowManager
@@ -41,6 +46,9 @@
import android.view.WindowInsets.Type.navigationBars
import android.view.WindowInsets.Type.statusBars
import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
@@ -50,6 +58,7 @@
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeStatus
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.sysui.KeyguardChangeListener
import com.android.wm.shell.sysui.ShellCommandHandler
@@ -58,6 +67,7 @@
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -70,8 +80,11 @@
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
import java.util.Optional
import java.util.function.Supplier
+import org.mockito.Mockito
+import org.mockito.kotlin.spy
/** Tests of [DesktopModeWindowDecorViewModel] */
@@ -79,6 +92,10 @@
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
+ @JvmField
+ @Rule
+ val setFlagsRule = SetFlagsRule()
+
@Mock private lateinit var mockDesktopModeWindowDecorFactory:
DesktopModeWindowDecoration.Factory
@Mock private lateinit var mockMainHandler: Handler
@@ -102,6 +119,7 @@
private val transactionFactory = Supplier<SurfaceControl.Transaction> {
SurfaceControl.Transaction()
}
+ private val windowDecorByTaskIdSpy = spy(SparseArray<DesktopModeWindowDecoration>())
private lateinit var shellInit: ShellInit
private lateinit var desktopModeOnInsetsChangedListener: DesktopModeOnInsetsChangedListener
@@ -110,6 +128,7 @@
@Before
fun setUp() {
shellInit = ShellInit(mockShellExecutor)
+ windowDecorByTaskIdSpy.clear()
desktopModeWindowDecorViewModel = DesktopModeWindowDecorViewModel(
mContext,
mockShellExecutor,
@@ -128,7 +147,8 @@
mockDesktopModeWindowDecorFactory,
mockInputMonitorFactory,
transactionFactory,
- mockRootTaskDisplayAreaOrganizer
+ mockRootTaskDisplayAreaOrganizer,
+ windowDecorByTaskIdSpy
)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -332,6 +352,67 @@
verify(decoration, times(1)).relayout(task)
}
+ @Test
+ fun testDestroyWindowDecoration_closesBeforeCleanup() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
+ val decoration = setUpMockDecorationForTask(task)
+ val inOrder = Mockito.inOrder(decoration, windowDecorByTaskIdSpy)
+
+ onTaskOpening(task)
+ desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+
+ inOrder.verify(decoration).close()
+ inOrder.verify(windowDecorByTaskIdSpy).remove(task.taskId)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_screenSizeBelowXLarge_decorNotCreated() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Update screen layout to be below minimum size
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory, never())
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_screenSizeBelowXLarge_displayRestrictionsOverridden_decorCreated() {
+ val mockitoSession: StaticMockitoSession = mockitoSession()
+ .strictness(Strictness.LENIENT)
+ .spyStatic(DesktopModeStatus::class.java)
+ .startMocking()
+ try {
+ // Simulate enforce display restrictions system property overridden to false
+ whenever(DesktopModeStatus.enforceDisplayRestrictions()).thenReturn(false)
+
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ // Update screen layout to be below minimum size
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_NORMAL
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
+ fun testWindowDecor_screenSizeXLarge_decorCreated() {
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN, focused = true)
+ task.configuration.screenLayout = SCREENLAYOUT_SIZE_XLARGE
+ setUpMockDecorationsForTasks(task)
+
+ onTaskOpening(task)
+ verify(mockDesktopModeWindowDecorFactory)
+ .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any())
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
new file mode 100644
index 0000000..4b008a7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/Android.bp
@@ -0,0 +1,51 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_libs_androidfw_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_libs_androidfw_license"],
+}
+
+cc_fuzz {
+ name: "resxmlparser_fuzzer",
+ srcs: [
+ "resxmlparser_fuzzer.cpp",
+ ],
+ host_supported: true,
+
+ static_libs: ["libgmock"],
+ target: {
+ android: {
+ shared_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libandroidfw",
+ "libbase",
+ "libbinder",
+ "libcutils",
+ "liblog",
+ "libutils",
+ ],
+ },
+ darwin: {
+ // libbinder is not supported on mac
+ enabled: false,
+ },
+ },
+
+ include_dirs: [
+ "system/incremental_delivery/incfs/util/include/",
+ ],
+
+ corpus: ["testdata/*"],
+ dictionary: "xmlparser_fuzzer.dict",
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
new file mode 100644
index 0000000..829a396
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/resxmlparser_fuzzer.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <memory>
+#include <cstdint>
+#include <cstddef>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "androidfw/ResourceTypes.h"
+
+static void populateDynamicRefTableWithFuzzedData(
+ android::DynamicRefTable& table,
+ FuzzedDataProvider& fuzzedDataProvider) {
+
+ const size_t numMappings = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 5);
+ for (size_t i = 0; i < numMappings; ++i) {
+ const uint8_t packageId = fuzzedDataProvider.ConsumeIntegralInRange<uint8_t>(0x02, 0x7F);
+
+ // Generate a package name
+ std::string packageName;
+ size_t packageNameLength = fuzzedDataProvider.ConsumeIntegralInRange<size_t>(1, 128);
+ for (size_t j = 0; j < packageNameLength; ++j) {
+ // Consume characters only in the ASCII range (0x20 to 0x7E) to ensure valid UTF-8
+ char ch = fuzzedDataProvider.ConsumeIntegralInRange<char>(0x20, 0x7E);
+ packageName.push_back(ch);
+ }
+
+ // Convert std::string to String16 for compatibility
+ android::String16 androidPackageName(packageName.c_str(), packageName.length());
+
+ // Add the mapping to the table
+ table.addMapping(androidPackageName, packageId);
+ }
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+ FuzzedDataProvider fuzzedDataProvider(data, size);
+
+ auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>();
+
+ // Populate the DynamicRefTable with fuzzed data
+ populateDynamicRefTableWithFuzzedData(*dynamic_ref_table, fuzzedDataProvider);
+
+ auto tree = android::ResXMLTree(std::move(dynamic_ref_table));
+
+ std::vector<uint8_t> xmlData = fuzzedDataProvider.ConsumeRemainingBytes<uint8_t>();
+ if (tree.setTo(xmlData.data(), xmlData.size()) != android::NO_ERROR) {
+ return 0; // Exit early if unable to parse XML data
+ }
+
+ tree.restart();
+
+ size_t len = 0;
+ auto code = tree.next();
+ if (code == android::ResXMLParser::START_TAG) {
+ // Access element name
+ auto name = tree.getElementName(&len);
+
+ // Access attributes of the current element
+ for (size_t i = 0; i < tree.getAttributeCount(); i++) {
+ // Access attribute name
+ auto attrName = tree.getAttributeName(i, &len);
+ }
+ } else if (code == android::ResXMLParser::TEXT) {
+ const auto text = tree.getText(&len);
+ }
+ return 0; // Non-zero return values are reserved for future use.
+}
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
new file mode 100644
index 0000000..417fec7
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/attributes.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child id="1">
+ <subchild type="A">Content A</subchild>
+ <subchild type="B">Content B</subchild>
+ </child>
+ <child id="2" extra="data">
+ <subchild type="C">Content C</subchild>
+ </child>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
new file mode 100644
index 0000000..7e13db5
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/basic.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <child1>Value 1</child1>
+ <child2>Value 2</child2>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
new file mode 100644
index 0000000..90cdf35
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/testdata/cdata.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+ <!-- Example with special characters and CDATA -->
+ <data><![CDATA[Some <encoded> data & other "special" characters]]></data>
+ <message>Hello & Welcome!</message>
+</root>
diff --git a/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
new file mode 100644
index 0000000..745ded4
--- /dev/null
+++ b/libs/androidfw/fuzz/resxmlparser_fuzzer/xmlparser_fuzzer.dict
@@ -0,0 +1,11 @@
+root_tag=<root>
+child_tag=<child>
+end_child_tag=</child>
+id_attr=id="
+type_attr=type="
+cdata_start=<![CDATA[
+cdata_end=]]>
+ampersand_entity=&
+xml_header=<?xml version="1.0" encoding="UTF-8"?>
+comment_start=<!--
+comment_end= -->
diff --git a/libs/hwui/VectorDrawable.cpp b/libs/hwui/VectorDrawable.cpp
index 2ea4e3f..af169f4 100644
--- a/libs/hwui/VectorDrawable.cpp
+++ b/libs/hwui/VectorDrawable.cpp
@@ -540,7 +540,7 @@
}
bool Tree::canReuseBitmap(Bitmap* bitmap, int width, int height) {
- return bitmap && width <= bitmap->width() && height <= bitmap->height();
+ return bitmap && width == bitmap->width() && height == bitmap->height();
}
void Tree::onPropertyChanged(TreeProperties* prop) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index b5f7caa..0d0af11 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -25,6 +25,7 @@
#include <android/sync.h>
#include <gui/TraceUtils.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
#include <ui/FatVector.h>
@@ -597,15 +598,14 @@
close(fence_clone);
sync_wait(bufferInfo->dequeue_fence, -1 /* forever */);
} else {
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
+ GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
// Skia will take ownership of the VkSemaphore and delete it once the wait
// has finished. The VkSemaphore also owns the imported fd, so it will
// close the fd when it is deleted.
- bufferInfo->skSurface->wait(1, &backendSemaphore);
+ bufferInfo->skSurface->wait(1, &beSemaphore);
// The following flush blocks the GPU immediately instead of waiting for
// other drawing ops. It seems dequeue_fence is not respected otherwise.
- // TODO: remove the flush after finding why backendSemaphore is not working.
+ // TODO: remove the flush after finding why beSemaphore is not working.
skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
}
}
@@ -626,7 +626,7 @@
SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
VkSemaphore semaphore)
: mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
- mGrBackendSemaphore.initVulkan(semaphore);
+ mGrBackendSemaphore = GrBackendSemaphores::MakeVk(mSemaphore);
}
~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
@@ -798,8 +798,7 @@
return UNKNOWN_ERROR;
}
- GrBackendSemaphore beSemaphore;
- beSemaphore.initVulkan(semaphore);
+ GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(semaphore);
// Skia will take ownership of the VkSemaphore and delete it once the wait has finished. The
// VkSemaphore also owns the imported fd, so it will close the fd when it is deleted.
diff --git a/libs/input/SpriteController.cpp b/libs/input/SpriteController.cpp
index 6dc45a6..6a32c5a 100644
--- a/libs/input/SpriteController.cpp
+++ b/libs/input/SpriteController.cpp
@@ -202,11 +202,13 @@
&& update.state.surfaceDrawn;
bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
- if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
- || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
- | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
- | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID
- | DIRTY_ICON_STYLE))))) {
+ if (update.state.surfaceControl != NULL &&
+ (becomingVisible || becomingHidden ||
+ (wantSurfaceVisibleAndDrawn &&
+ (update.state.dirty &
+ (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER |
+ DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE |
+ DIRTY_DRAW_DROP_SHADOW))))) {
needApplyTransaction = true;
if (wantSurfaceVisibleAndDrawn
@@ -235,13 +237,15 @@
update.state.transformationMatrix.dtdy);
}
- if (wantSurfaceVisibleAndDrawn
- && (becomingVisible
- || (update.state.dirty & (DIRTY_HOTSPOT | DIRTY_ICON_STYLE)))) {
+ if (wantSurfaceVisibleAndDrawn &&
+ (becomingVisible ||
+ (update.state.dirty &
+ (DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW)))) {
Parcel p;
p.writeInt32(static_cast<int32_t>(update.state.icon.style));
p.writeFloat(update.state.icon.hotSpotX);
p.writeFloat(update.state.icon.hotSpotY);
+ p.writeBool(update.state.icon.drawNativeDropShadow);
// Pass cursor metadata in the sprite surface so that when Android is running as a
// client OS (e.g. ARC++) the host OS can get the requested cursor metadata and
@@ -388,12 +392,13 @@
uint32_t dirty;
if (icon.isValid()) {
mLocked.state.icon.bitmap = icon.bitmap.copy(ANDROID_BITMAP_FORMAT_RGBA_8888);
- if (!mLocked.state.icon.isValid()
- || mLocked.state.icon.hotSpotX != icon.hotSpotX
- || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ if (!mLocked.state.icon.isValid() || mLocked.state.icon.hotSpotX != icon.hotSpotX ||
+ mLocked.state.icon.hotSpotY != icon.hotSpotY ||
+ mLocked.state.icon.drawNativeDropShadow != icon.drawNativeDropShadow) {
mLocked.state.icon.hotSpotX = icon.hotSpotX;
mLocked.state.icon.hotSpotY = icon.hotSpotY;
- dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ mLocked.state.icon.drawNativeDropShadow = icon.drawNativeDropShadow;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_DRAW_DROP_SHADOW;
} else {
dirty = DIRTY_BITMAP;
}
@@ -404,7 +409,7 @@
}
} else if (mLocked.state.icon.isValid()) {
mLocked.state.icon.bitmap.reset();
- dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT | DIRTY_ICON_STYLE | DIRTY_DRAW_DROP_SHADOW;
} else {
return; // setting to invalid icon and already invalid so nothing to do
}
diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h
index 04ecb38..35776e9 100644
--- a/libs/input/SpriteController.h
+++ b/libs/input/SpriteController.h
@@ -151,6 +151,7 @@
DIRTY_HOTSPOT = 1 << 6,
DIRTY_DISPLAY_ID = 1 << 7,
DIRTY_ICON_STYLE = 1 << 8,
+ DIRTY_DRAW_DROP_SHADOW = 1 << 9,
};
/* Describes the state of a sprite.
diff --git a/libs/input/SpriteIcon.h b/libs/input/SpriteIcon.h
index 0939af4..7d45d02 100644
--- a/libs/input/SpriteIcon.h
+++ b/libs/input/SpriteIcon.h
@@ -40,7 +40,7 @@
PointerIconStyle style{PointerIconStyle::TYPE_NULL};
float hotSpotX{};
float hotSpotY{};
- bool drawNativeDropShadow{false};
+ bool drawNativeDropShadow{};
inline bool isValid() const { return bitmap.isValid() && !bitmap.isEmpty(); }
diff --git a/location/java/android/location/provider/ForwardGeocodeRequest.java b/location/java/android/location/provider/ForwardGeocodeRequest.java
index 8f227b1..89d14fb 100644
--- a/location/java/android/location/provider/ForwardGeocodeRequest.java
+++ b/location/java/android/location/provider/ForwardGeocodeRequest.java
@@ -260,7 +260,11 @@
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/location/java/android/location/provider/ReverseGeocodeRequest.java b/location/java/android/location/provider/ReverseGeocodeRequest.java
index 57c9047..2107707 100644
--- a/location/java/android/location/provider/ReverseGeocodeRequest.java
+++ b/location/java/android/location/provider/ReverseGeocodeRequest.java
@@ -207,7 +207,11 @@
mCallingAttributionTag = null;
}
- /** Sets the attribution tag. */
+ /**
+ * Sets the attribution tag.
+ *
+ * @param attributionTag The attribution tag to associate with the request.
+ */
@NonNull
public Builder setCallingAttributionTag(@NonNull String attributionTag) {
mCallingAttributionTag = attributionTag;
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 60ab1a4..a53a8ce 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -275,17 +275,23 @@
if (o == null || getClass() != o.getClass()) return false;
final AudioMix that = (AudioMix) o;
+ boolean tokenMatch = android.media.audiopolicy.Flags.audioMixOwnership()
+ ? Objects.equals(this.mToken, that.mToken)
+ : true;
return Objects.equals(this.mRouteFlags, that.mRouteFlags)
&& Objects.equals(this.mRule, that.mRule)
&& Objects.equals(this.mMixType, that.mMixType)
&& Objects.equals(this.mFormat, that.mFormat)
- && Objects.equals(this.mToken, that.mToken);
+ && tokenMatch;
}
/** @hide */
@Override
public int hashCode() {
- return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+ if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
+ }
+ return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
}
@Override
diff --git a/media/java/android/media/metrics/PlaybackSession.java b/media/java/android/media/metrics/PlaybackSession.java
index f8dd756..6223acf 100644
--- a/media/java/android/media/metrics/PlaybackSession.java
+++ b/media/java/android/media/metrics/PlaybackSession.java
@@ -24,7 +24,10 @@
import java.util.Objects;
/**
- * An instances of this class represents a session of media playback.
+ * An instance of this class represents a session of media playback used to report playback
+ * metrics and events.
+ *
+ * Create a new instance using {@link MediaMetricsManager#createPlaybackSession}.
*/
public final class PlaybackSession implements AutoCloseable {
private final @NonNull String mId;
@@ -80,6 +83,21 @@
mManager.reportTrackChangeEvent(mId, event);
}
+ /**
+ * A session ID is used to identify a unique playback and to tie together lower-level
+ * playback components.
+ *
+ * Associate this session with a {@link MediaCodec} by passing the ID into
+ * {@link MediaFormat} through {@link MediaFormat#LOG_SESSION_ID} when
+ * creating the {@link MediaCodec}.
+ *
+ * Associate this session with an {@link AudioTrack} by calling
+ * {@link AudioTrack#setLogSessionId}.
+ *
+ * Associate this session with {@link MediaDrm} and {@link MediaCrypto} by calling
+ * {@link MediaDrm#getPlaybackComponent} and then calling
+ * {@link PlaybackComponent#setLogSessionId}.
+ */
public @NonNull LogSessionId getSessionId() {
return mLogSessionId;
}
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 8396005..0fc80dd 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -2895,6 +2895,10 @@
jint offset,
jint size,
std::shared_ptr<C2Buffer> *buffer) {
+ if ((offset + size) > context->capacity()) {
+ ALOGW("extractBufferFromContext: offset + size provided exceed capacity");
+ return;
+ }
*buffer = context->toC2Buffer(offset, size);
if (*buffer == nullptr) {
if (!context->mMemory) {
@@ -2995,18 +2999,15 @@
"MediaCodec.LinearBlock#obtain method to obtain a compatible buffer.");
return;
}
- sp<CryptoInfosWrapper> cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
- jint sampleSize = 0;
+ sp<CryptoInfosWrapper> cryptoInfos = nullptr;
+ jint sampleSize = totalSize;
if (cryptoInfoArray != nullptr) {
+ cryptoInfos = new CryptoInfosWrapper{decltype(cryptoInfos->value)()};
extractCryptoInfosFromObjectArray(env,
&sampleSize,
&cryptoInfos->value,
cryptoInfoArray,
&errorDetailMsg);
- } else {
- sampleSize = totalSize;
- std::unique_ptr<CodecCryptoInfo> cryptoInfo{new MediaCodecCryptoInfo(totalSize)};
- cryptoInfos->value.push_back(std::move(cryptoInfo));
}
if (env->ExceptionCheck()) {
// Creation of cryptoInfo failed. Let the exception bubble up.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 7f3792d..752ebdf 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -98,6 +98,7 @@
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.os.flags-aconfig-cc",
"libnativedisplay",
],
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 53699bc..0a22314 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -124,11 +124,11 @@
}
float AMotionEvent_getXOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getXOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawXOffset();
}
float AMotionEvent_getYOffset(const AInputEvent* motion_event) {
- return static_cast<const MotionEvent*>(motion_event)->getYOffset();
+ return static_cast<const MotionEvent*>(motion_event)->getRawYOffset();
}
float AMotionEvent_getXPrecision(const AInputEvent* motion_event) {
diff --git a/nfc/Android.bp b/nfc/Android.bp
index b6bc40d..7dd16ba 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -38,6 +38,7 @@
name: "framework-nfc",
libs: [
"unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+ "framework-permission-s",
],
static_libs: [
"android.nfc.flags-aconfig-java",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 9d0221a..54f1421 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -194,13 +194,13 @@
package android.nfc.cardemulation {
public final class CardEmulation {
- method public boolean categoryAllowsForegroundPreference(String);
+ method @Deprecated public boolean categoryAllowsForegroundPreference(String);
method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public java.util.List<java.lang.String> getAidsForPreferredPaymentService();
method public java.util.List<java.lang.String> getAidsForService(android.content.ComponentName, String);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public CharSequence getDescriptionForPreferredPaymentService();
method public static android.nfc.cardemulation.CardEmulation getInstance(android.nfc.NfcAdapter);
- method @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
- method public int getSelectionModeForCategory(String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public String getRouteDestinationForPreferredPaymentService();
+ method @Deprecated public int getSelectionModeForCategory(String);
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
@@ -268,9 +268,9 @@
ctor public PollingFrame(int, @Nullable byte[], int, int);
method public int describeContents();
method @NonNull public byte[] getData();
- method public int getGain();
method public int getTimestamp();
method public int getType();
+ method public int getVendorSpecificGain();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.nfc.cardemulation.PollingFrame> CREATOR;
field @FlaggedApi("android.nfc.nfc_read_polling_loop") public static final int POLLING_LOOP_TYPE_A = 65; // 0x41
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index ea58504..e55f540 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -27,6 +27,7 @@
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -278,13 +279,22 @@
* @param category The category, e.g. {@link #CATEGORY_PAYMENT}
* @return whether AIDs in the category can be handled by a service
* specified by the foreground app.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
@SuppressWarnings("NonUserGetterCalled")
+ @Deprecated
public boolean categoryAllowsForegroundPreference(String category) {
+ Context contextAsUser = mContext.createContextAsUser(
+ UserHandle.of(UserHandle.myUserId()), 0);
+ RoleManager roleManager = contextAsUser.getSystemService(RoleManager.class);
+ if (roleManager.isRoleAvailable(RoleManager.ROLE_WALLET)) {
+ return true;
+ }
if (CATEGORY_PAYMENT.equals(category)) {
boolean preferForeground = false;
- Context contextAsUser = mContext.createContextAsUser(
- UserHandle.of(UserHandle.myUserId()), 0);
try {
preferForeground = Settings.Secure.getInt(
contextAsUser.getContentResolver(),
@@ -309,7 +319,12 @@
* to pick a service if there is a conflict.
* @param category The category, for example {@link #CATEGORY_PAYMENT}
* @return the selection mode for the passed in category
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will be done in a AID
+ * category agnostic manner.
*/
+ @Deprecated
public int getSelectionModeForCategory(String category) {
if (CATEGORY_PAYMENT.equals(category)) {
boolean paymentRegistered = false;
@@ -792,8 +807,15 @@
* (e.g. eSE/eSE1, eSE2, etc.).
* 2. "OffHost" if the payment service does not specify secure element
* name.
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public String getRouteDestinationForPreferredPaymentService() {
try {
@@ -836,8 +858,15 @@
* Returns a user-visible description of the preferred payment service.
*
* @return the preferred payment service description
+ *
+ * @deprecated see {@link android.app.role.RoleManager#ROLE_WALLET}. The definition of the
+ * Preferred Payment service is no longer valid. All routings will go to the Wallet Holder app.
+ * A payment service will be selected automatically based on registered AIDs. In the case of
+ * multiple services that register for the same payment AID, the selection will be done on
+ * an alphabetical order based on the component names.
*/
@RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO)
+ @Deprecated
@Nullable
public CharSequence getDescriptionForPreferredPaymentService() {
try {
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 994f4ae..29d7bdf 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -157,7 +157,7 @@
mType = frame.getInt(KEY_POLLING_LOOP_TYPE);
byte[] data = frame.getByteArray(KEY_POLLING_LOOP_DATA);
mData = (data == null) ? new byte[0] : data;
- mGain = frame.getByte(KEY_POLLING_LOOP_GAIN);
+ mGain = frame.getInt(KEY_POLLING_LOOP_GAIN, -1);
mTimestamp = frame.getInt(KEY_POLLING_LOOP_TIMESTAMP);
}
@@ -194,8 +194,9 @@
/**
* Returns the gain representing the field strength of the NFC field when this polling loop
* frame was observed.
+ * @return the gain or -1 if there is no gain measurement associated with this frame.
*/
- public int getGain() {
+ public int getVendorSpecificGain() {
return mGain;
}
@@ -227,7 +228,9 @@
public Bundle toBundle() {
Bundle frame = new Bundle();
frame.putInt(KEY_POLLING_LOOP_TYPE, getType());
- frame.putByte(KEY_POLLING_LOOP_GAIN, (byte) getGain());
+ if (getVendorSpecificGain() != -1) {
+ frame.putInt(KEY_POLLING_LOOP_GAIN, (byte) getVendorSpecificGain());
+ }
frame.putByteArray(KEY_POLLING_LOOP_DATA, getData());
frame.putInt(KEY_POLLING_LOOP_TIMESTAMP, getTimestamp());
return frame;
@@ -236,7 +239,7 @@
@Override
public String toString() {
return "PollingFrame { Type: " + (char) getType()
- + ", gain: " + getGain()
+ + ", gain: " + getVendorSpecificGain()
+ ", timestamp: " + Integer.toUnsignedString(getTimestamp())
+ ", data: [" + HexFormat.ofDelimiter(" ").formatHex(getData()) + "] }";
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 4e1f4ee..d7a2e36 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -21,14 +21,13 @@
import android.content.Context
import android.credentials.CredentialManager
import android.credentials.GetCredentialRequest
-import android.credentials.GetCredentialResponse
-import android.credentials.GetCredentialException
import android.credentials.GetCandidateCredentialsResponse
import android.credentials.GetCandidateCredentialsException
import android.credentials.CredentialOption
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.credentials.selection.ProviderData
+import android.graphics.BlendMode
import android.graphics.drawable.Icon
import android.os.Bundle
import android.os.CancellationSignal
@@ -123,13 +122,10 @@
// TODO(b/324635774): Use callback for validating. If the request is coming
// directly from the view, there should be a corresponding callback, otherwise
// we should fail fast,
- val getCredCallback = getCredManCallback(structure)
if (getCredRequest == null) {
Log.i(TAG, "No credential manager request found")
callback.onFailure("No credential manager request found")
return
- } else if (getCredCallback == null) {
- Log.i(TAG, "No credential manager callback found")
}
val credentialManager: CredentialManager =
getSystemService(Context.CREDENTIAL_SERVICE) as CredentialManager
@@ -353,6 +349,7 @@
val sliceBuilder = InlineSuggestionUi
.newContentBuilder(pendingIntent)
.setTitle(displayName)
+ icon.setTintBlendMode(BlendMode.DST)
sliceBuilder.setStartIcon(icon)
if (primaryEntry.credentialType ==
CredentialType.PASSKEY && duplicateDisplayNameForPasskeys[displayName] == true) {
@@ -526,42 +523,6 @@
TODO("Not yet implemented")
}
- private fun getCredManCallback(structure: AssistStructure): OutcomeReceiver<
- GetCredentialResponse, GetCredentialException>? {
- return traverseStructureForCallback(structure)
- }
-
- private fun traverseStructureForCallback(
- structure: AssistStructure
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val windowNodes: List<AssistStructure.WindowNode> =
- structure.run {
- (0 until windowNodeCount).map { getWindowNodeAt(it) }
- }
-
- windowNodes.forEach { windowNode: AssistStructure.WindowNode ->
- return traverseNodeForCallback(windowNode.rootViewNode)
- }
- return null
- }
-
- private fun traverseNodeForCallback(
- viewNode: AssistStructure.ViewNode
- ): OutcomeReceiver<GetCredentialResponse, GetCredentialException>? {
- val children: List<AssistStructure.ViewNode> =
- viewNode.run {
- (0 until childCount).map { getChildAt(it) }
- }
-
- children.forEach { childNode: AssistStructure.ViewNode ->
- if (childNode.isFocused() && childNode.credentialManagerCallback != null) {
- return childNode.credentialManagerCallback
- }
- return traverseNodeForCallback(childNode)
- }
- return null
- }
-
private fun getCredManRequest(
structure: AssistStructure,
sessionId: Int,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
index a7b5c36..e43b09e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/BottomSheet.kt
@@ -39,7 +39,6 @@
import com.android.credentialmanager.ui.theme.EntryShape
import kotlinx.coroutines.launch
-
/** Draws a modal bottom sheet with the same styles and effects shared by various flows. */
@Composable
@OptIn(ExperimentalMaterial3Api::class)
@@ -73,7 +72,7 @@
dragHandle = null,
// Never take over the full screen. We always want to leave some top scrim space
// for exiting and viewing the underlying app to help a user gain context.
- modifier = Modifier.padding(top = 56.dp),
+ modifier = Modifier.padding(top = 72.dp),
)
} else {
val scope = rememberCoroutineScope()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
index c68ae8b..006a2d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Cards.kt
@@ -16,6 +16,7 @@
package com.android.credentialmanager.common.ui
+import android.credentials.flags.Flags
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.WindowInsets
@@ -63,7 +64,7 @@
modifier = Modifier.padding(
start = 24.dp,
end = 24.dp,
- bottom = 18.dp,
+ bottom = if (Flags.selectorUiImprovementsEnabled()) 8.dp else 18.dp,
top = if (topAppBar == null) 24.dp else 0.dp
).fillMaxWidth().wrapContentHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 56bd066..642a377 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -21,6 +21,7 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@@ -94,7 +95,7 @@
label = {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
- modifier = Modifier.fillMaxWidth().padding(
+ modifier = Modifier.fillMaxWidth().heightIn(min = 56.dp).padding(
// Total end padding should be 16dp, but the suggestion chip itself
// has 8dp horizontal elements padding
horizontal = 8.dp, vertical = 16.dp,
@@ -152,14 +153,14 @@
},
)
}
- } else if (entrySecondLineText != null) {
+ } else if (!entrySecondLineText.isNullOrBlank()) {
BodySmallText(
text = entrySecondLineText,
enforceOneLine = enforceOneLine,
onTextLayout = onTextLayout,
)
}
- if (entryThirdLineText != null) {
+ if (!entryThirdLineText.isNullOrBlank()) {
BodySmallText(
text = entryThirdLineText,
enforceOneLine = enforceOneLine,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
index 3ebdd20..ff421bc 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/InlinePresentationFactory.kt
@@ -21,63 +21,20 @@
import android.content.Context
import android.util.Size
import android.widget.inline.InlinePresentationSpec
-import androidx.autofill.inline.common.TextViewStyle
-import androidx.autofill.inline.common.ViewStyle
-import androidx.autofill.inline.UiVersions
-import androidx.autofill.inline.UiVersions.Style
-import androidx.autofill.inline.v1.InlineSuggestionUi
-import androidx.core.content.ContextCompat
-import android.util.TypedValue
-import android.graphics.Typeface
-
class InlinePresentationsFactory {
companion object {
- private const val googleSansMediumFontFamily = "google-sans-medium"
- private const val googleSansTextFontFamily = "google-sans-text"
- // There is no min width required for now but this is needed for the spec builder
- private const val minInlineWidth = 5000
+ // There is no max width required for now but this is needed for the spec builder
+ private const val maxInlineWidth = 5000
fun modifyInlinePresentationSpec(context: Context,
originalSpec: InlinePresentationSpec): InlinePresentationSpec {
return InlinePresentationSpec.Builder(Size(originalSpec.minSize.width, originalSpec
.minSize.height),
- Size(minInlineWidth, originalSpec
+ Size(maxInlineWidth, originalSpec
.maxSize.height))
- .setStyle(UiVersions.newStylesBuilder().addStyle(getStyle(context)).build())
- .build()
- }
-
-
- fun getStyle(context: Context): Style {
- val textColorPrimary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_primary)
- val textColorSecondary = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.text_secondary)
- val textColorBackground = ContextCompat.getColor(context,
- com.android.credentialmanager.R.color.inline_background)
- val chipHorizontalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.horizontal_chip_padding)
- val chipVerticalPadding = context.resources.getDimensionPixelSize(com.android
- .credentialmanager.R.dimen.vertical_chip_padding)
- return InlineSuggestionUi.newStyleBuilder()
- .setChipStyle(
- ViewStyle.Builder().setPadding(chipHorizontalPadding,
- chipVerticalPadding,
- chipHorizontalPadding, chipVerticalPadding).build()
- )
- .setTitleStyle(
- TextViewStyle.Builder().setTextColor(textColorPrimary).setTextSize
- (TypedValue.COMPLEX_UNIT_DIP, 14F)
- .setTypeface(googleSansMediumFontFamily,
- Typeface.NORMAL).setBackgroundColor(textColorBackground)
- .build()
- )
- .setSubtitleStyle(TextViewStyle.Builder().setTextColor(textColorSecondary)
- .setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12F).setTypeface
- (googleSansTextFontFamily, Typeface.NORMAL).setBackgroundColor
- (textColorBackground).build())
+ .setStyle(originalSpec.getStyle())
.build()
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 4ef7760..af78573 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -336,7 +336,7 @@
if (!footerDescription.isNullOrBlank()) {
item {
Row(modifier = Modifier.fillMaxWidth().wrapContentHeight()) {
- BodySmallText(text = footerDescription)
+ BodyMediumText(text = footerDescription)
}
}
item { Divider(thickness = 24.dp, color = Color.Transparent) }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 748c798..b9c9d89 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -18,7 +18,6 @@
import android.credentials.flags.Flags.selectorUiImprovementsEnabled
import android.graphics.drawable.Drawable
-import android.text.TextUtils
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.result.ActivityResult
import androidx.activity.result.IntentSenderRequest
@@ -431,6 +430,14 @@
val hasSingleEntry = primaryPageCredentialEntryList.size +
primaryPageLockedEntryList.size == 1
+ val areAllPasswordsOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+ primaryPageCredentialEntryList.all {
+ it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSWORD
+ }
+ val areAllPasskeysOnPrimaryScreen = primaryPageLockedEntryList.isEmpty() &&
+ primaryPageCredentialEntryList.all {
+ it.sortedCredentialEntryList.first().credentialType == CredentialType.PASSKEY
+ }
item {
if (requestDisplayInfo.preferIdentityDocUi) {
HeadlineText(
@@ -447,28 +454,30 @@
HeadlineText(
text = stringResource(
if (hasSingleEntry) {
- val singleEntryType = primaryPageCredentialEntryList.firstOrNull()
- ?.sortedCredentialEntryList?.firstOrNull()?.credentialType
- if (singleEntryType == CredentialType.PASSKEY)
+ if (areAllPasskeysOnPrimaryScreen)
R.string.get_dialog_title_use_passkey_for
- else if (singleEntryType == CredentialType.PASSWORD)
+ else if (areAllPasswordsOnPrimaryScreen)
R.string.get_dialog_title_use_password_for
else if (authenticationEntryList.isNotEmpty())
R.string.get_dialog_title_unlock_options_for
else R.string.get_dialog_title_use_sign_in_for
} else {
- if (authenticationEntryList.isNotEmpty() ||
- sortedUserNameToCredentialEntryList.any { perNameEntryList ->
- perNameEntryList.sortedCredentialEntryList.any { entry ->
- entry.credentialType != CredentialType.PASSWORD &&
- entry.credentialType != CredentialType.PASSKEY
- }
+ if (areAllPasswordsOnPrimaryScreen)
+ R.string.get_dialog_title_choose_password_for
+ else if (areAllPasskeysOnPrimaryScreen)
+ R.string.get_dialog_title_choose_passkey_for
+ else if (primaryPageLockedEntryList.isNotEmpty() ||
+ primaryPageCredentialEntryList.any {
+ it.sortedCredentialEntryList.first().credentialType !=
+ CredentialType.PASSWORD &&
+ it.sortedCredentialEntryList.first().credentialType !=
+ CredentialType.PASSKEY
}
- ) // For an unknown / locked entry, it's not true that it is
+ ) // An unknown typed / locked entry exists, and we can't say it is
// already saved, strictly speaking. Hence use a different title
- // without the mention of "saved"
+ // without the mention of "saved".
R.string.get_dialog_title_choose_sign_in_for
- else
+ else // All entries on the primary screen are passkeys or passwords
R.string.get_dialog_title_choose_saved_sign_in_for
},
requestDisplayInfo.appName
@@ -490,8 +499,11 @@
showMoreForTruncatedEntry.value = it.hasVisualOverflow
},
hasSingleEntry = hasSingleEntry,
+ hasSingleProvider = singleProviderId != null,
shouldOverrideIcon = entry.isDefaultIconPreferredAsSingleProvider &&
(singleProviderId != null),
+ shouldRemoveTypeDisplayName = areAllPasswordsOnPrimaryScreen ||
+ areAllPasskeysOnPrimaryScreen
)
}
primaryPageLockedEntryList.forEach {
@@ -754,11 +766,19 @@
enforceOneLine: Boolean = false,
onTextLayout: (TextLayoutResult) -> Unit = {},
// Make optional since the secondary page doesn't care about this value.
- hasSingleEntry: Boolean? = null,
+ hasSingleEntry: Boolean = false,
// For primary page only, if all display entries come from the same provider AND if that
// provider has opted in via isDefaultIconPreferredAsSingleProvider, then we override the
// display icon to the default icon for the given credential type.
shouldOverrideIcon: Boolean = false,
+ // For primary page only, if all entries come from the same provider, then remove that provider
+ // name from each entry, since that provider icon + name will be shown front and central at
+ // the top of the bottom sheet.
+ hasSingleProvider: Boolean = false,
+ // For primary page only, if all visible entrise are of the same type and that type is passkey
+ // or password, then set this bit to true to remove the type display name from each entry for
+ // simplification, since that info is mentioned in the title.
+ shouldRemoveTypeDisplayName: Boolean = false,
) {
val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
userAndDisplayNameForPasskey(
@@ -786,19 +806,18 @@
else if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
entryHeadlineText = username,
- entrySecondLineText =
- (if (hasSingleEntry != null && hasSingleEntry)
- if (credentialEntryInfo.credentialType == CredentialType.PASSKEY ||
- credentialEntryInfo.credentialType == CredentialType.PASSWORD)
- listOf(displayName)
+ entrySecondLineText = displayName,
+ entryThirdLineText =
+ (if (hasSingleEntry)
+ if (shouldRemoveTypeDisplayName) emptyList()
// Still show the type display name for all non-password/passkey types since it won't be
// mentioned in the bottom sheet heading.
- else listOf(displayName, credentialEntryInfo.credentialTypeDisplayName)
+ else listOf(credentialEntryInfo.credentialTypeDisplayName)
else listOf(
- displayName,
- credentialEntryInfo.credentialTypeDisplayName,
- credentialEntryInfo.providerDisplayName
- )).filterNot(TextUtils::isEmpty).let { itemsToDisplay ->
+ if (shouldRemoveTypeDisplayName) null
+ else credentialEntryInfo.credentialTypeDisplayName,
+ if (hasSingleProvider) null else credentialEntryInfo.providerDisplayName
+ )).filterNot{ it.isNullOrBlank() }.let { itemsToDisplay ->
if (itemsToDisplay.isEmpty()) null
else itemsToDisplay.joinToString(
separator = stringResource(R.string.get_dialog_sign_in_type_username_separator)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index ef40188..e35acae 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -25,6 +25,7 @@
import com.android.credentialmanager.model.get.CredentialEntryInfo
import com.android.credentialmanager.model.get.RemoteEntryInfo
import com.android.internal.util.Preconditions
+import java.time.Instant
data class GetCredentialUiState(
val isRequestForAllOptions: Boolean,
@@ -156,11 +157,17 @@
userNameToCredentialEntryMap.values.forEach {
it.sortWith(comparator)
}
- // Transform to list of PerUserNameCredentialEntryLists and then sort across usernames
+ // Transform to list of PerUserNameCredentialEntryLists and then sort the outer list (of
+ // entries grouped by username / entryGroupId) based on the latest timestamp within that
+ // PerUserNameCredentialEntryList
val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
PerUserNameCredentialEntryList(it.key, it.value)
}.sortedWith(
- compareByDescending { it.sortedCredentialEntryList.first().lastUsedTimeMillis }
+ compareByDescending {
+ it.sortedCredentialEntryList.maxByOrNull{ entry ->
+ entry.lastUsedTimeMillis ?: Instant.MIN
+ }?.lastUsedTimeMillis ?: Instant.MIN
+ }
)
return ProviderDisplayInfo(
@@ -211,7 +218,7 @@
val typePriorityMap: Map<String, Int>,
) : Comparator<CredentialEntryInfo> {
override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
- // First prefer passkey type for its security benefits
+ // First rank by priorities of each credential type.
if (p0.rawCredentialType != p1.rawCredentialType) {
val p0Priority = typePriorityMap.getOrDefault(
p0.rawCredentialType, PriorityHints.PRIORITY_DEFAULT
@@ -225,6 +232,7 @@
return 1
}
}
+ // Then rank by last used timestamps.
val p0LastUsedTimeMillis = p0.lastUsedTimeMillis
val p1LastUsedTimeMillis = p1.lastUsedTimeMillis
// Then order by last used timestamp
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index 98a5a67..79c810c 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -54,6 +54,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
lint: {
@@ -85,6 +86,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
aaptflags: ["--product tablet"],
@@ -118,6 +120,7 @@
"androidx.lifecycle_lifecycle-extensions",
"android.content.pm.flags-aconfig-java",
"android.os.flags-aconfig-java",
+ "android.multiuser.flags-aconfig-java",
],
aaptflags: ["--product tv"],
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index 634e067..cf2f85e 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -20,7 +20,6 @@
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
-import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -28,10 +27,10 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.pm.Flags;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.AssetFileDescriptor;
+import android.Manifest;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -201,7 +200,7 @@
params.setPermissionState(Manifest.permission.USE_FULL_SCREEN_INTENT,
PackageInstaller.SessionParams.PERMISSION_STATE_DENIED);
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
final PackageInstaller.InstallInfo result = installer.readInstallInfo(pfd,
debugPathName, 0);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index e95a8e6..45bfe54 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -31,7 +31,6 @@
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
import android.content.pm.InstallSourceInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
@@ -400,10 +399,7 @@
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
-1 /* defaultValue */);
final SessionInfo info = mInstaller.getSessionInfo(sessionId);
- String resolvedPath = null;
- if (info != null && Flags.getResolvedApkPath()) {
- resolvedPath = info.getResolvedBaseApkPath();
- }
+ String resolvedPath = info != null ? info.getResolvedBaseApkPath() : null;
if (info == null || !info.isSealed() || resolvedPath == null) {
Log.w(TAG, "Session " + sessionId + " in funky state; ignoring");
finish();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 221ca4f..8f5d07c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -166,6 +166,7 @@
messageBuilder.append(getString(
R.string.uninstall_application_text_current_user_clone_profile));
} else if (Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& customUserManager.isPrivateProfile()
&& customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
messageBuilder.append(
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
index 22caabd..aeabbd5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt
@@ -25,7 +25,6 @@
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
-import android.content.pm.Flags
import android.content.pm.PackageInfo
import android.content.pm.PackageInstaller
import android.content.pm.PackageInstaller.SessionInfo
@@ -363,7 +362,7 @@
params.setPermissionState(
Manifest.permission.USE_FULL_SCREEN_INTENT, SessionParams.PERMISSION_STATE_DENIED
)
- if (pfd != null && Flags.readInstallInfo()) {
+ if (pfd != null) {
try {
val installInfo = packageInstaller.readInstallInfo(pfd, debugPathName, 0)
params.setAppPackageName(installInfo.packageName)
@@ -426,8 +425,7 @@
if (PackageInstaller.ACTION_CONFIRM_INSTALL == intent.action) {
val info = packageInstaller.getSessionInfo(sessionId)
- val resolvedPath =
- if (Flags.getResolvedApkPath()) info?.resolvedBaseApkPath else null
+ val resolvedPath = info?.resolvedBaseApkPath
if (info == null || !info.isSealed || resolvedPath == null) {
Log.w(LOG_TAG, "Session $sessionId in funky state; ignoring")
return InstallAborted(ABORT_REASON_INTERNAL_ERROR)
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
index 0fc1845..c6b6d36 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/UninstallRepository.kt
@@ -235,7 +235,9 @@
messageString = context.getString(
R.string.uninstall_application_text_current_user_clone_profile
)
- } else if (Flags.allowPrivateProfile() && customUserManager!!.isPrivateProfile()) {
+ } else if (Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && customUserManager!!.isPrivateProfile()) {
// TODO(b/324244123): Get these Strings from a User Property API.
messageString = context.getString(
R.string.uninstall_application_text_current_user_private_profile
diff --git a/packages/SettingsLib/DataStore/tests/Android.bp b/packages/SettingsLib/DataStore/tests/Android.bp
new file mode 100644
index 0000000..8770dfa
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/Android.bp
@@ -0,0 +1,24 @@
+package {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_app {
+ name: "SettingsLibDataStoreShell",
+ platform_apis: true,
+}
+
+android_robolectric_test {
+ name: "SettingsLibDataStoreTest",
+ srcs: ["src/**/*"],
+ static_libs: [
+ "SettingsLibDataStore",
+ "androidx.test.ext.junit",
+ "guava",
+ "mockito-robolectric-prebuilt", // mockito deps order matters!
+ "mockito-kotlin2",
+ ],
+ java_resource_dirs: ["config"],
+ instrumentation_for: "SettingsLibDataStoreShell",
+ coverage_libs: ["SettingsLibDataStore"],
+ upstream: true,
+}
diff --git a/packages/SettingsLib/DataStore/tests/AndroidManifest.xml b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
new file mode 100644
index 0000000..ffc24e4
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.datastore.test">
+
+ <application android:debuggable="true" />
+</manifest>
diff --git a/packages/SettingsLib/DataStore/tests/config/robolectric.properties b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
new file mode 100644
index 0000000..bb791dc
--- /dev/null
+++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.datastore
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicInteger
+import org.junit.Assert.assertThrows
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.never
+import org.mockito.kotlin.reset
+import org.mockito.kotlin.verify
+
+@RunWith(AndroidJUnit4::class)
+class ObserverTest {
+ @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+ @Mock private lateinit var observer1: Observer
+
+ @Mock private lateinit var observer2: Observer
+
+ @Mock private lateinit var executor: Executor
+
+ private val observable = DataObservable()
+
+ @Test
+ fun addObserver_sameExecutor() {
+ observable.addObserver(observer1, executor)
+ observable.addObserver(observer1, executor)
+ }
+
+ @Test
+ fun addObserver_differentExecutor() {
+ observable.addObserver(observer1, executor)
+ assertThrows(IllegalStateException::class.java) {
+ observable.addObserver(observer1, MoreExecutors.directExecutor())
+ }
+ }
+
+ @Test
+ fun addObserver_weaklyReferenced() {
+ val counter = AtomicInteger()
+ var observer: Observer? = Observer { counter.incrementAndGet() }
+ observable.addObserver(observer!!, MoreExecutors.directExecutor())
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+
+ // trigger GC, the observer callback should not be invoked
+ @Suppress("unused")
+ observer = null
+ System.gc()
+ System.runFinalization()
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ assertThat(counter.get()).isEqualTo(1)
+ }
+
+ @Test
+ fun addObserver_notifyObservers_removeObserver() {
+ observable.addObserver(observer1, MoreExecutors.directExecutor())
+ observable.addObserver(observer2, executor)
+
+ observable.notifyChange(ChangeReason.DELETE)
+
+ verify(observer1).onChanged(ChangeReason.DELETE)
+ verify(observer2, never()).onChanged(any())
+ verify(executor).execute(any())
+
+ reset(observer1, executor)
+ observable.removeObserver(observer2)
+
+ observable.notifyChange(ChangeReason.UPDATE)
+ verify(observer1).onChanged(ChangeReason.UPDATE)
+ verify(executor, never()).execute(any())
+ }
+
+ @Test
+ fun notifyChange_addObserverWithinCallback() {
+ // ConcurrentModificationException is raised if it is not implemented correctly
+ observable.addObserver(
+ { observable.addObserver(observer1, executor) },
+ MoreExecutors.directExecutor()
+ )
+ observable.notifyChange(ChangeReason.UPDATE)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
index fc8de80..0a469b8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/MainSwitchPreference.kt
@@ -47,7 +47,7 @@
onCheckedChange = model.onCheckedChange,
paddingStart = 20.dp,
paddingEnd = 20.dp,
- paddingVertical = 18.dp,
+ paddingVertical = 24.dp,
)
}
}
@@ -55,7 +55,7 @@
@Preview
@Composable
-fun MainSwitchPreferencePreview() {
+private fun MainSwitchPreferencePreview() {
SettingsTheme {
Column {
MainSwitchPreference(object : SwitchPreferenceModel {
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e489bc5..2889ce2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -1703,6 +1703,7 @@
public boolean isPrivateProfile() {
return android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
&& UserManager.USER_TYPE_PROFILE_PRIVATE.equals(mProfileType);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
index da1fd55..0c7d6f0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothLeBroadcastMetadataExt.kt
@@ -311,7 +311,7 @@
}
builder.apply {
- setSourceDevice(device, sourceAddrType)
+ setSourceDevice(device, addrType)
setSourceAdvertisingSid(sourceAdvertiserSid)
setBroadcastId(broadcastId)
setBroadcastName(broadcastName)
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index bdb5871..e34c50e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -44,7 +44,6 @@
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
import android.annotation.TargetApi;
-import android.app.Notification;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.ComponentName;
@@ -67,6 +66,7 @@
import com.android.settingslib.media.flags.Flags;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -74,16 +74,50 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** InfoMediaManager provide interface to get InfoMediaDevice list. */
@RequiresApi(Build.VERSION_CODES.R)
-public abstract class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager {
+ /** Callback for notifying device is added, removed and attributes changed. */
+ public interface MediaDeviceCallback {
- private static final String TAG = "InfoMediaManager";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ /**
+ * Callback for notifying MediaDevice list is added.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListAdded(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying MediaDevice list is removed.
+ *
+ * @param devices the MediaDevice list
+ */
+ void onDeviceListRemoved(@NonNull List<MediaDevice> devices);
+
+ /**
+ * Callback for notifying connected MediaDevice is changed.
+ *
+ * @param id the id of MediaDevice
+ */
+ void onConnectedDeviceChanged(@Nullable String id);
+
+ /**
+ * Callback for notifying that transferring is failed.
+ *
+ * @param reason the reason that the request has failed. Can be one of followings: {@link
+ * android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_REJECTED}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE}, {@link
+ * android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
+ */
+ void onRequestFailed(int reason);
+ }
/** Checked exception that signals the specified package is not present in the system. */
public static class PackageNotAvailableException extends Exception {
@@ -92,19 +126,22 @@
}
}
+ private static final String TAG = "InfoMediaManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
+ @NonNull protected final Context mContext;
@NonNull protected final String mPackageName;
+ private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
private MediaDevice mCurrentConnectedDevice;
private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
/* package */ InfoMediaManager(
- Context context,
+ @NonNull Context context,
@NonNull String packageName,
- Notification notification,
- LocalBluetoothManager localBluetoothManager) {
- super(context, notification);
-
+ @NonNull LocalBluetoothManager localBluetoothManager) {
+ mContext = context;
mBluetoothManager = localBluetoothManager;
mPackageName = packageName;
}
@@ -113,7 +150,6 @@
public static InfoMediaManager createInstance(
Context context,
@Nullable String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
// The caller is only interested in system routes (headsets, built-in speakers, etc), and is
@@ -125,17 +161,14 @@
if (Flags.useMediaRouter2ForInfoMediaManager()) {
try {
- return new RouterInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new RouterInfoMediaManager(context, packageName, localBluetoothManager);
} catch (PackageNotAvailableException ex) {
// TODO: b/293578081 - Propagate this exception to callers for proper handling.
Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName);
- return new NoOpInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new NoOpInfoMediaManager(context, packageName, localBluetoothManager);
}
} else {
- return new ManagerInfoMediaManager(
- context, packageName, notification, localBluetoothManager);
+ return new ManagerInfoMediaManager(context, packageName, localBluetoothManager);
}
}
@@ -227,6 +260,48 @@
Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap);
}
+ protected final MediaDevice findMediaDevice(@NonNull String id) {
+ for (MediaDevice mediaDevice : mMediaDevices) {
+ if (mediaDevice.getId().equals(id)) {
+ return mediaDevice;
+ }
+ }
+ Log.e(TAG, "findMediaDevice() can't find device with id: " + id);
+ return null;
+ }
+
+ protected final void registerCallback(MediaDeviceCallback callback) {
+ if (!mCallbacks.contains(callback)) {
+ mCallbacks.add(callback);
+ }
+ }
+
+ protected final void unregisterCallback(MediaDeviceCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void dispatchDeviceListAdded(@NonNull List<MediaDevice> devices) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onDeviceListAdded(new ArrayList<>(devices));
+ }
+ }
+
+ private void dispatchConnectedDeviceChanged(String id) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onConnectedDeviceChanged(id);
+ }
+ }
+
+ protected void dispatchOnRequestFailed(int reason) {
+ for (MediaDeviceCallback callback : getCallbacks()) {
+ callback.onRequestFailed(reason);
+ }
+ }
+
+ private Collection<MediaDeviceCallback> getCallbacks() {
+ return new CopyOnWriteArrayList<>(mCallbacks);
+ }
+
/**
* Get current device that played media.
* @return MediaDevice
@@ -433,7 +508,7 @@
protected final synchronized void refreshDevices() {
rebuildDeviceList();
- dispatchDeviceListAdded();
+ dispatchDeviceListAdded(mMediaDevices);
}
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5925492..63056b6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -138,8 +138,7 @@
}
mInfoMediaManager =
- InfoMediaManager.createInstance(
- context, packageName, notification, mLocalBluetoothManager);
+ InfoMediaManager.createInstance(context, packageName, mLocalBluetoothManager);
}
/**
@@ -505,9 +504,9 @@
return new CopyOnWriteArrayList<>(mCallbacks);
}
- class MediaDeviceCallback implements MediaManager.MediaDeviceCallback {
+ class MediaDeviceCallback implements InfoMediaManager.MediaDeviceCallback {
@Override
- public void onDeviceListAdded(List<MediaDevice> devices) {
+ public void onDeviceListAdded(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.clear();
mMediaDevices.addAll(devices);
@@ -637,7 +636,7 @@
}
@Override
- public void onDeviceListRemoved(List<MediaDevice> devices) {
+ public void onDeviceListRemoved(@NonNull List<MediaDevice> devices) {
synchronized (mMediaDevicesLock) {
mMediaDevices.removeAll(devices);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 453e807..c4fac35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
@@ -54,9 +53,8 @@
/* package */ ManagerInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
mRouterManager = MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
deleted file mode 100644
index 8bebd6e..0000000
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaManager.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.settingslib.media;
-
-import android.app.Notification;
-import android.content.Context;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * MediaManager provide interface to get MediaDevice list.
- */
-public abstract class MediaManager {
-
- private static final String TAG = "MediaManager";
-
- protected final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>();
- protected final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
-
- protected Context mContext;
- protected Notification mNotification;
-
- MediaManager(Context context, Notification notification) {
- mContext = context;
- mNotification = notification;
- }
-
- protected void registerCallback(MediaDeviceCallback callback) {
- if (!mCallbacks.contains(callback)) {
- mCallbacks.add(callback);
- }
- }
-
- protected void unregisterCallback(MediaDeviceCallback callback) {
- if (mCallbacks.contains(callback)) {
- mCallbacks.remove(callback);
- }
- }
-
- protected MediaDevice findMediaDevice(String id) {
- for (MediaDevice mediaDevice : mMediaDevices) {
- if (mediaDevice.getId().equals(id)) {
- return mediaDevice;
- }
- }
- Log.e(TAG, "findMediaDevice() can't found device");
- return null;
- }
-
- protected void dispatchDeviceListAdded() {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListAdded(new ArrayList<>(mMediaDevices));
- }
- }
-
- protected void dispatchDeviceListRemoved(List<MediaDevice> devices) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onDeviceListRemoved(devices);
- }
- }
-
- protected void dispatchConnectedDeviceChanged(String id) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onConnectedDeviceChanged(id);
- }
- }
-
- protected void dispatchOnRequestFailed(int reason) {
- for (MediaDeviceCallback callback : getCallbacks()) {
- callback.onRequestFailed(reason);
- }
- }
-
- private Collection<MediaDeviceCallback> getCallbacks() {
- return new CopyOnWriteArrayList<>(mCallbacks);
- }
-
- /**
- * Callback for notifying device is added, removed and attributes changed.
- */
- public interface MediaDeviceCallback {
-
- /**
- * Callback for notifying MediaDevice list is added.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListAdded(List<MediaDevice> devices);
-
- /**
- * Callback for notifying MediaDevice list is removed.
- *
- * @param devices the MediaDevice list
- */
- void onDeviceListRemoved(List<MediaDevice> devices);
-
- /**
- * Callback for notifying connected MediaDevice is changed.
- *
- * @param id the id of MediaDevice
- */
- void onConnectedDeviceChanged(String id);
-
- /**
- * Callback for notifying that transferring is failed.
- *
- * @param reason the reason that the request has failed. Can be one of followings:
- * {@link android.media.MediaRoute2ProviderService#REASON_UNKNOWN_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_REJECTED},
- * {@link android.media.MediaRoute2ProviderService#REASON_NETWORK_ERROR},
- * {@link android.media.MediaRoute2ProviderService#REASON_ROUTE_NOT_AVAILABLE},
- * {@link android.media.MediaRoute2ProviderService#REASON_INVALID_COMMAND},
- */
- void onRequestFailed(int reason);
- }
-}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
index ea4de39..2b8c2dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.RouteListingPreference;
@@ -38,13 +37,29 @@
*/
// TODO - b/293578081: Remove once PackageNotAvailableException is propagated to library clients.
/* package */ final class NoOpInfoMediaManager extends InfoMediaManager {
+ /**
+ * Placeholder routing session to return as active session of {@link NoOpInfoMediaManager}.
+ *
+ * <p>Returning this routing session avoids crashes in {@link InfoMediaManager} and maintains
+ * the same client-facing behaviour as if no routing session was found for the target package
+ * name.
+ *
+ * <p>Volume and max volume are set to {@code -1} to emulate a non-existing routing session in
+ * {@link #getSessionVolume()} and {@link #getSessionVolumeMax()}.
+ */
+ private static final RoutingSessionInfo PLACEHOLDER_SESSION =
+ new RoutingSessionInfo.Builder(
+ /* id */ "FAKE_ROUTING_SESSION", /* clientPackageName */ "")
+ .addSelectedRoute(/* routeId */ "FAKE_SELECTED_ROUTE_ID")
+ .setVolumeMax(-1)
+ .setVolume(-1)
+ .build();
NoOpInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager) {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
}
@Override
@@ -120,7 +135,7 @@
@NonNull
@Override
protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
- return Collections.emptyList();
+ return List.of(PLACEHOLDER_SESSION);
}
@Nullable
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
index df03167..9c82cb1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java
@@ -17,7 +17,6 @@
package com.android.settingslib.media;
import android.annotation.SuppressLint;
-import android.app.Notification;
import android.content.Context;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
@@ -71,10 +70,9 @@
/* package */ RouterInfoMediaManager(
Context context,
@NonNull String packageName,
- Notification notification,
LocalBluetoothManager localBluetoothManager)
throws PackageNotAvailableException {
- super(context, packageName, notification, localBluetoothManager);
+ super(context, packageName, localBluetoothManager);
MediaRouter2 router = null;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
index a5c63be..7e3f38b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/data/repository/SpatializerRepository.kt
@@ -18,23 +18,18 @@
import android.media.AudioDeviceAttributes
import android.media.Spatializer
-import androidx.concurrent.futures.DirectExecutor
import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface SpatializerRepository {
- /** Returns true when head tracking is enabled and false the otherwise. */
- val isHeadTrackingAvailable: StateFlow<Boolean>
+ /**
+ * Returns true when head tracking is available for the [audioDeviceAttributes] and false the
+ * otherwise.
+ */
+ suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean
/**
* Returns true when Spatial audio feature is supported for the [audioDeviceAttributes] and
@@ -65,22 +60,14 @@
class SpatializerRepositoryImpl(
private val spatializer: Spatializer,
- coroutineScope: CoroutineScope,
private val backgroundContext: CoroutineContext,
) : SpatializerRepository {
- override val isHeadTrackingAvailable: StateFlow<Boolean> =
- callbackFlow {
- val listener =
- Spatializer.OnHeadTrackerAvailableListener { _, available ->
- launch { send(available) }
- }
- spatializer.addOnHeadTrackerAvailableListener(DirectExecutor.INSTANCE, listener)
- awaitClose { spatializer.removeOnHeadTrackerAvailableListener(listener) }
- }
- .onStart { emit(spatializer.isHeadTrackerAvailable) }
- .flowOn(backgroundContext)
- .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
+ override suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean {
+ return withContext(backgroundContext) { spatializer.hasHeadTracker(audioDeviceAttributes) }
+ }
override suspend fun isSpatialAudioAvailableForDevice(
audioDeviceAttributes: AudioDeviceAttributes
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
index 0347403..5589733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/media/domain/interactor/SpatializerInteractor.kt
@@ -18,17 +18,17 @@
import android.media.AudioDeviceAttributes
import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.StateFlow
class SpatializerInteractor(private val repository: SpatializerRepository) {
- /** Checks if head tracking is available. */
- val isHeadTrackingAvailable: StateFlow<Boolean>
- get() = repository.isHeadTrackingAvailable
-
+ /** Checks if spatial audio is available. */
suspend fun isSpatialAudioAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
repository.isSpatialAudioAvailableForDevice(audioDeviceAttributes)
+ /** Checks if head tracking is available. */
+ suspend fun isHeadTrackingAvailable(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
+ repository.isHeadTrackingAvailableForDevice(audioDeviceAttributes)
+
/** Checks if spatial audio is enabled for the [audioDeviceAttributes]. */
suspend fun isSpatialAudioEnabled(audioDeviceAttributes: AudioDeviceAttributes): Boolean =
repository.getSpatialAudioCompatibleDevices().contains(audioDeviceAttributes)
diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
index 794cf83..7719c4b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractor.kt
@@ -48,6 +48,9 @@
/** Checks if [notificationPolicy] allows media. */
val isMediaAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowMedia() }
+ /** Checks if [notificationPolicy] allows system sounds. */
+ val isSystemAllowed: Flow<Boolean?> = notificationPolicy.map { it?.allowSystem() }
+
/** Checks if [notificationPolicy] allows ringer. */
val isRingerAllowed: Flow<Boolean?> =
notificationPolicy.map { policy ->
@@ -62,31 +65,29 @@
areAlarmsAllowed.filterNotNull(),
isMediaAllowed.filterNotNull(),
isRingerAllowed.filterNotNull(),
- ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed ->
- if (zenMode.zenMode == Settings.Global.ZEN_MODE_NO_INTERRUPTIONS) {
- return@combine true
+ isSystemAllowed.filterNotNull(),
+ ) { zenMode, areAlarmsAllowed, isMediaAllowed, isRingerAllowed, isSystemAllowed ->
+ when (zenMode.zenMode) {
+ // Everything is muted
+ Settings.Global.ZEN_MODE_NO_INTERRUPTIONS -> return@combine true
+ Settings.Global.ZEN_MODE_ALARMS ->
+ return@combine stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION ||
+ stream.value == AudioManager.STREAM_SYSTEM
+ Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS -> {
+ when {
+ stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed ->
+ return@combine true
+ stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed ->
+ return@combine true
+ stream.value == AudioManager.STREAM_SYSTEM && !isSystemAllowed ->
+ return@combine true
+ (stream.value == AudioManager.STREAM_RING ||
+ stream.value == AudioManager.STREAM_NOTIFICATION) && !isRingerAllowed ->
+ return@combine true
+ }
+ }
}
-
- val isNotificationOrRing =
- stream.value == AudioManager.STREAM_RING ||
- stream.value == AudioManager.STREAM_NOTIFICATION
- if (isNotificationOrRing && zenMode.zenMode == Settings.Global.ZEN_MODE_ALARMS) {
- return@combine true
- }
- if (zenMode.zenMode != Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS) {
- return@combine false
- }
-
- if (stream.value == AudioManager.STREAM_ALARM && !areAlarmsAllowed) {
- return@combine true
- }
- if (stream.value == AudioManager.STREAM_MUSIC && !isMediaAllowed) {
- return@combine true
- }
- if (isNotificationOrRing && !isRingerAllowed) {
- return@combine true
- }
-
return@combine false
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 65b73ca..d5444cf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -46,14 +46,14 @@
/**
* Wrapper the [.getInternetIconResource] for testing compatibility.
*/
- class InternetIconInjector(protected val context: Context) {
+ open class InternetIconInjector(protected val context: Context) {
/**
* Returns the Internet icon for a given RSSI level.
*
* @param noInternet True if a connected Wi-Fi network cannot access the Internet
* @param level The number of bars to show (0-4)
*/
- fun getIcon(noInternet: Boolean, level: Int): Drawable? {
+ open fun getIcon(noInternet: Boolean, level: Int): Drawable? {
return context.getDrawable(getInternetIconResource(level, noInternet))
}
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
index 1ad7d49..fe83ffb 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/applications/ApplicationsStateTest.java
@@ -319,7 +319,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectApps() {
- mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mEntry.showInPersonalTab = true;
mEntry.mProfileType = UserManager.USER_TYPE_FULL_SYSTEM;
@@ -334,7 +335,8 @@
@Test
public void testPrivateProfileFilterDisplaysCorrectAppsWhenFlagDisabled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mEntry.showInPersonalTab = false;
mEntry.mProfileType = UserManager.USER_TYPE_PROFILE_PRIVATE;
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
index c647cbb..f0185b9 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java
@@ -64,22 +64,21 @@
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() {
- InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null);
assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class);
}
@Test
@RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() {
- InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null);
+ InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null);
assertThat(manager).isInstanceOf(RouterInfoMediaManager.class);
}
@@ -87,7 +86,7 @@
@RequiresFlagsDisabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER)
public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() {
InfoMediaManager manager =
- InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null, null);
+ InfoMediaManager.createInstance(mContext, mContext.getPackageName(), null);
assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 827d8fa..3b18aa3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -72,6 +72,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -499,6 +500,7 @@
verify(mApplicationsState, never()).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void removeProfileApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
@@ -573,6 +575,7 @@
verify(mApplicationsState).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void removeOwnerApp_workprofileExists_doResumeIfNeededLocked_shouldClearEntries()
throws RemoteException {
@@ -654,6 +657,7 @@
verify(mApplicationsState).clearEntries();
}
+ @Ignore("b/328332487")
@Test
public void noAppRemoved_workprofileExists_doResumeIfNeededLocked_shouldNotClearEntries()
throws RemoteException {
@@ -773,6 +777,7 @@
assertThat(primaryUserApp.shouldShowInPersonalTab(um, appInfo.uid)).isTrue();
}
+ @Ignore("b/328332487")
@Test
public void shouldShowInPersonalTab_userProfilePreU_returnsFalse() {
UserManager um = RuntimeEnvironment.application.getSystemService(UserManager.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c159d5e..d85d253 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -91,7 +91,7 @@
@Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
- private MediaManager.MediaDeviceCallback mCallback;
+ private InfoMediaManager.MediaDeviceCallback mCallback;
@Mock
private MediaSessionManager mMediaSessionManager;
@Mock
@@ -109,8 +109,7 @@
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new ManagerInfoMediaManager(
- mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 9a7d4f1..693b7d0 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -35,7 +36,6 @@
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.MediaRoute2Info;
-import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
import com.android.settingslib.bluetooth.A2dpProfile;
@@ -75,8 +75,6 @@
private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
- private InfoMediaManager mInfoMediaManager;
- @Mock
private LocalBluetoothManager mLocalBluetoothManager;
@Mock
private LocalMediaManager.DeviceCallback mCallback;
@@ -87,8 +85,6 @@
@Mock
private LocalBluetoothProfileManager mLocalProfileManager;
@Mock
- private MediaRouter2Manager mMediaRouter2Manager;
- @Mock
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
@@ -97,6 +93,7 @@
private Context mContext;
private LocalMediaManager mLocalMediaManager;
+ private InfoMediaManager mInfoMediaManager;
private ShadowBluetoothAdapter mShadowBluetoothAdapter;
private InfoMediaDevice mInfoMediaDevice1;
private InfoMediaDevice mInfoMediaDevice2;
@@ -116,10 +113,16 @@
when(mLocalProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
+ // Need to call constructor to initialize final fields.
+ mInfoMediaManager = mock(
+ InfoMediaManager.class,
+ withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager));
+
mInfoMediaDevice1 = spy(new InfoMediaDevice(mContext, mRouteInfo1));
mInfoMediaDevice2 = new InfoMediaDevice(mContext, mRouteInfo2);
- mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
- mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager =
+ new LocalMediaManager(
+ mContext, mLocalBluetoothManager, mInfoMediaManager, TEST_PACKAGE_NAME);
mLocalMediaManager.mAudioManager = mAudioManager;
}
@@ -146,7 +149,6 @@
mLocalMediaManager.registerCallback(mCallback);
assertThat(mLocalMediaManager.connectDevice(device)).isTrue();
-
verify(mInfoMediaManager).connectToDevice(device);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
deleted file mode 100644
index 46e724d..0000000
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaManagerTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.media;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-@RunWith(RobolectricTestRunner.class)
-public class MediaManagerTest {
-
- private static final String TEST_ID = "test_id";
-
- @Mock
- private MediaManager.MediaDeviceCallback mCallback;
- @Mock
- private MediaDevice mDevice;
-
- private MediaManager mMediaManager;
- private Context mContext;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
-
- when(mDevice.getId()).thenReturn(TEST_ID);
-
- mMediaManager = new MediaManager(mContext, null) {};
- }
-
- @Test
- public void dispatchDeviceListAdded_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListAdded();
-
- verify(mCallback).onDeviceListAdded(any());
- }
-
- @Test
- public void dispatchDeviceListRemoved_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchDeviceListRemoved(mMediaManager.mMediaDevices);
-
- verify(mCallback).onDeviceListRemoved(mMediaManager.mMediaDevices);
- }
-
- @Test
- public void dispatchActiveDeviceChanged_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchConnectedDeviceChanged(TEST_ID);
-
- verify(mCallback).onConnectedDeviceChanged(TEST_ID);
- }
-
- @Test
- public void findMediaDevice_idExist_shouldReturnMediaDevice() {
- mMediaManager.mMediaDevices.add(mDevice);
-
- final MediaDevice device = mMediaManager.findMediaDevice(TEST_ID);
-
- assertThat(device.getId()).isEqualTo(mDevice.getId());
- }
-
- @Test
- public void findMediaDevice_idNotExist_shouldReturnNull() {
- mMediaManager.mMediaDevices.add(mDevice);
-
- final MediaDevice device = mMediaManager.findMediaDevice("123");
-
- assertThat(device).isNull();
- }
-
- @Test
- public void dispatchOnRequestFailed_registerCallback_shouldDispatchCallback() {
- mMediaManager.registerCallback(mCallback);
-
- mMediaManager.dispatchOnRequestFailed(1);
-
- verify(mCallback).onRequestFailed(1);
- }
-
-}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
new file mode 100644
index 0000000..d630301
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/**
+ * Tests for {@link NoOpInfoMediaManager} to avoid exceptions in {@link InfoMediaManager}.
+ *
+ * <p>While {@link NoOpInfoMediaManager} should not perform any actions, it should still return
+ * placeholder information in certain cases to not change the behaviour of {@link InfoMediaManager}
+ * and prevent crashes.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class NoOpInfoMediaManagerTest {
+ private InfoMediaManager mInfoMediaManager;
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ mInfoMediaManager =
+ new NoOpInfoMediaManager(
+ mContext,
+ /* packageName */ "FAKE_PACKAGE_NAME",
+ /* localBluetoothManager */ null);
+ }
+
+ @Test
+ public void getSessionVolumeMax_returnsNotFound() {
+ assertThat(mInfoMediaManager.getSessionVolumeMax()).isEqualTo(-1);
+ }
+
+ @Test
+ public void getSessionVolume_returnsNotFound() {
+ assertThat(mInfoMediaManager.getSessionVolume()).isEqualTo(-1);
+ }
+
+ @Test
+ public void getSessionName_returnsNull() {
+ assertThat(mInfoMediaManager.getSessionName()).isNull();
+ }
+
+ @Test
+ public void getRoutingSessionForPackage_returnsPlaceholderSession() {
+ // Make sure we return a placeholder routing session so that we avoid OOB exceptions.
+ assertThat(mInfoMediaManager.getRoutingSessionsForPackage()).hasSize(1);
+ }
+
+ @Test
+ public void getSelectedMediaDevices_returnsEmptyList() {
+ assertThat(mInfoMediaManager.getSelectedMediaDevices()).isEmpty();
+ }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index add3134..8f8445d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -115,6 +115,10 @@
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.CHARGING_SOUNDS_ENABLED,
Settings.Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED,
+ Settings.Global.Wearable.AUTO_BEDTIME_MODE,
Settings.Global.FORCE_ENABLE_PSS_PROFILING,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
+ Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 2fa1c6e..a490b6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -154,6 +154,7 @@
Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT,
Settings.Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
Settings.Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED,
+ Settings.Secure.EMERGENCY_THERMAL_ALERT_DISABLED,
Settings.Secure.HUSH_GESTURE_USED,
Settings.Secure.IN_CALL_NOTIFICATION_ENABLED,
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0a0760..6def40b 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -452,6 +452,7 @@
VALIDATORS.put(Global.Wearable.WRIST_DETECTION_AUTO_LOCKING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Global.Wearable.CONSISTENT_NOTIFICATION_BLOCKING_ENABLED, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.AUTO_BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.FORCE_ENABLE_PSS_PROFILING, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 2535fdb..4cdf98cb 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -235,6 +235,7 @@
VALIDATORS.put(Secure.MANUAL_RINGER_TOGGLE_COUNT, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.EMERGENCY_THERMAL_ALERT_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.IN_CALL_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index ce0257f..2a8eb9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -72,6 +72,7 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -157,6 +158,9 @@
"/product/etc/aconfig_flags.pb",
"/vendor/etc/aconfig_flags.pb");
+ private static final String APEX_DIR = "/apex";
+ private static final String APEX_ACONFIG_PATH_SUFFIX = "/etc/aconfig_flags.pb";
+
/**
* This tag is applied to all aconfig default value-loaded flags.
*/
@@ -238,7 +242,7 @@
private int mNextHistoricalOpIdx;
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private Map<String, Map<String, String>> mNamespaceDefaults;
public static final int SETTINGS_TYPE_GLOBAL = 0;
@@ -332,23 +336,29 @@
mHistoricalOperations = Build.IS_DEBUGGABLE
? new ArrayList<>(HISTORICAL_OPERATION_COUNT) : null;
+ mNamespaceDefaults = new HashMap<>();
+
synchronized (mLock) {
readStateSyncLocked();
if (Flags.loadAconfigDefaults()) {
if (isConfigSettingsKey(mKey)) {
- loadAconfigDefaultValuesLocked();
+ loadAconfigDefaultValuesLocked(sAconfigTextProtoFilesOnDevice);
}
}
+ if (Flags.loadApexAconfigProtobufs()) {
+ if (isConfigSettingsKey(mKey)) {
+ List<String> apexProtoPaths = listApexProtoPaths();
+ loadAconfigDefaultValuesLocked(apexProtoPaths);
+ }
+ }
}
}
@GuardedBy("mLock")
- private void loadAconfigDefaultValuesLocked() {
- mNamespaceDefaults = new HashMap<>();
-
- for (String fileName : sAconfigTextProtoFilesOnDevice) {
+ private void loadAconfigDefaultValuesLocked(List<String> filePaths) {
+ for (String fileName : filePaths) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
} catch (IOException e) {
@@ -357,13 +367,41 @@
}
}
+ private List<String> listApexProtoPaths() {
+ LinkedList<String> paths = new LinkedList();
+
+ File apexDirectory = new File(APEX_DIR);
+ if (!apexDirectory.isDirectory()) {
+ return paths;
+ }
+
+ File[] subdirs = apexDirectory.listFiles();
+ if (subdirs == null) {
+ return paths;
+ }
+
+ for (File prefix : subdirs) {
+ // For each mainline modules, there are two directories, one <modulepackage>/,
+ // and one <modulepackage>@<versioncode>/. Just read the former.
+ if (prefix.getAbsolutePath().contains("@")) {
+ continue;
+ }
+
+ File protoPath = new File(prefix + APEX_ACONFIG_PATH_SUFFIX);
+ if (!protoPath.exists()) {
+ continue;
+ }
+
+ paths.add(protoPath.getAbsolutePath());
+ }
+ return paths;
+ }
+
@VisibleForTesting
@GuardedBy("mLock")
public void addAconfigDefaultValuesFromMap(
@NonNull Map<String, Map<String, String>> defaultMap) {
- if (mNamespaceDefaults != null) {
- mNamespaceDefaults.putAll(defaultMap);
- }
+ mNamespaceDefaults.putAll(defaultMap);
}
@VisibleForTesting
@@ -447,7 +485,7 @@
return names;
}
- @Nullable
+ @NonNull
public Map<String, Map<String, String>> getAconfigDefaultValues() {
synchronized (mLock) {
return mNamespaceDefaults;
@@ -519,9 +557,9 @@
return false;
}
- // Aconfig flags are always boot stable, so we anytime we write one, we staged it to be
+ // Aconfig flags are always boot stable, so we anytime we write one, we stage it to be
// applied on reboot.
- if (Flags.stageAllAconfigFlags() && mNamespaceDefaults != null) {
+ if (Flags.stageAllAconfigFlags()) {
int slashIndex = name.indexOf("/");
boolean stageFlag = isConfigSettingsKey(mKey)
&& slashIndex != -1
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index e5086e8..2e14e9b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -25,3 +25,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "load_apex_aconfig_protobufs"
+ namespace: "core_experiments_team_internal"
+ description: "When enabled, loads aconfig default values in apex flag protobufs into DeviceConfig on boot."
+ bug: "327383546"
+ is_fixed_read_only: true
+}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 28cdc6d..09d076e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -626,9 +626,6 @@
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
- Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index ea30c69..33362a2 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -25,7 +25,6 @@
import android.aconfig.Aconfig.parsed_flag;
import android.aconfig.Aconfig.parsed_flags;
import android.os.Looper;
-import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -231,38 +230,6 @@
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_LOAD_ACONFIG_DEFAULTS)
- public void testAddingAconfigMapOnNullIsNoOp() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState = new SettingsState(
- InstrumentationRegistry.getContext(), lock, mSettingsFile, configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
- parsed_flags flags = parsed_flags
- .newBuilder()
- .addParsedFlag(parsed_flag
- .newBuilder()
- .setPackage("com.android.flags")
- .setName("flag5")
- .setNamespace("test_namespace")
- .setDescription("test flag")
- .addBug("12345678")
- .setState(Aconfig.flag_state.DISABLED)
- .setPermission(Aconfig.flag_permission.READ_WRITE))
- .build();
-
- synchronized (lock) {
- Map<String, Map<String, String>> defaults = new HashMap<>();
- settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
- settingsState.addAconfigDefaultValuesFromMap(defaults);
-
- assertEquals(null, settingsState.getAconfigDefaultValues());
- }
-
- }
-
- @Test
public void testInvalidAconfigProtoDoesNotCrash() {
Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 0c02f56..eb2d13d 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -566,9 +566,6 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_LOGO" />
- <!-- Permission required for CTS test - android.server.biometrics -->
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
-
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index cc2e84c..34b4beea 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -155,6 +155,7 @@
"jsr330",
"lottie",
"LowLightDreamLib",
+ "TraceurCommon",
"motion_tool_lib",
"notification_flags_lib",
"PlatformComposeCore",
@@ -301,6 +302,7 @@
"androidx.compose.material_material-icons-extended",
"androidx.activity_activity-compose",
"androidx.compose.animation_animation-graphics",
+ "TraceurCommon",
],
}
@@ -308,6 +310,7 @@
name: "SystemUI-tests",
use_resource_processor: true,
manifest: "tests/AndroidManifest-base.xml",
+ resource_dirs: [],
additional_manifests: ["tests/AndroidManifest.xml"],
srcs: [
"tests/src/**/*.kt",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 12e8f57..98591e9 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -273,6 +273,9 @@
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
+ <!-- to change spatial audio -->
+ <uses-permission android:name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
+
<!-- to access ResolverRankerServices -->
<uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
index 085fc29..88181e7 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/AccessibilityMenuService.java
@@ -56,10 +56,12 @@
implements View.OnTouchListener {
public static final String PACKAGE_NAME = AccessibilityMenuService.class.getPackageName();
+ public static final String PACKAGE_TESTS = ".tests";
public static final String INTENT_TOGGLE_MENU = ".toggle_menu";
public static final String INTENT_HIDE_MENU = ".hide_menu";
public static final String INTENT_GLOBAL_ACTION = ".global_action";
public static final String INTENT_GLOBAL_ACTION_EXTRA = "GLOBAL_ACTION";
+ public static final String INTENT_OPEN_BLOCKED = "OPEN_BLOCKED";
private static final String TAG = "A11yMenuService";
private static final long BUFFER_MILLISECONDS_TO_PREVENT_UPDATE_FAILURE = 100L;
@@ -192,7 +194,7 @@
IntentFilter hideMenuFilter = new IntentFilter();
hideMenuFilter.addAction(Intent.ACTION_SCREEN_OFF);
- hideMenuFilter.addAction(PACKAGE_NAME + INTENT_HIDE_MENU);
+ hideMenuFilter.addAction(INTENT_HIDE_MENU);
// Including WRITE_SECURE_SETTINGS enforces that we only listen to apps
// with the restricted WRITE_SECURE_SETTINGS permission who broadcast this intent.
@@ -200,7 +202,7 @@
Manifest.permission.WRITE_SECURE_SETTINGS, null,
Context.RECEIVER_EXPORTED);
registerReceiver(mToggleMenuReceiver,
- new IntentFilter(PACKAGE_NAME + INTENT_TOGGLE_MENU),
+ new IntentFilter(INTENT_TOGGLE_MENU),
Manifest.permission.WRITE_SECURE_SETTINGS, null,
Context.RECEIVER_EXPORTED);
@@ -245,8 +247,9 @@
* @return {@code true} if successful, {@code false} otherwise.
*/
private boolean performGlobalActionInternal(int globalAction) {
- Intent intent = new Intent(PACKAGE_NAME + INTENT_GLOBAL_ACTION);
+ Intent intent = new Intent(INTENT_GLOBAL_ACTION);
intent.putExtra(INTENT_GLOBAL_ACTION_EXTRA, globalAction);
+ intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
sendBroadcast(intent);
Log.i("A11yMenuService", "Broadcasting global action " + globalAction);
return performGlobalAction(globalAction);
@@ -410,9 +413,16 @@
private void toggleVisibility() {
boolean locked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
- if (!locked && SystemClock.uptimeMillis() - mLastTimeTouchedOutside
- > BUTTON_CLICK_TIMEOUT) {
- mA11yMenuLayout.toggleVisibility();
+ if (!locked) {
+ if (SystemClock.uptimeMillis() - mLastTimeTouchedOutside
+ > BUTTON_CLICK_TIMEOUT) {
+ mA11yMenuLayout.toggleVisibility();
+ }
+ } else {
+ // Broadcast for testing.
+ Intent intent = new Intent(INTENT_OPEN_BLOCKED);
+ intent.setPackage(PACKAGE_NAME + PACKAGE_TESTS);
+ sendBroadcast(intent);
}
}
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
index 72c1092..6546b87 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java
@@ -23,6 +23,7 @@
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_RECENTS;
import static android.accessibilityservice.AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT;
+import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_OPEN_BLOCKED;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_GLOBAL_ACTION_EXTRA;
import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_HIDE_MENU;
@@ -65,6 +66,7 @@
import org.junit.runner.RunWith;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
@RunWith(AndroidJUnit4.class)
@@ -75,12 +77,11 @@
private static final int TIMEOUT_SERVICE_STATUS_CHANGE_S = 5;
private static final int TIMEOUT_UI_CHANGE_S = 5;
private static final int NO_GLOBAL_ACTION = -1;
- private static final String INPUT_KEYEVENT_KEYCODE_BACK = "input keyevent KEYCODE_BACK";
- private static final String TEST_PIN = "1234";
private static Instrumentation sInstrumentation;
private static UiAutomation sUiAutomation;
- private static AtomicInteger sLastGlobalAction;
+ private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
+ private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false);
private static AccessibilityManager sAccessibilityManager;
private static PowerManager sPowerManager;
@@ -122,8 +123,6 @@
() -> sAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK).stream().filter(
info -> info.getId().contains(serviceName)).count() == 1);
-
- sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION);
context.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -131,20 +130,28 @@
sLastGlobalAction.set(
intent.getIntExtra(INTENT_GLOBAL_ACTION_EXTRA, NO_GLOBAL_ACTION));
}},
- new IntentFilter(PACKAGE_NAME + INTENT_GLOBAL_ACTION),
+ new IntentFilter(INTENT_GLOBAL_ACTION),
+ null, null, Context.RECEIVER_EXPORTED);
+
+ context.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Received notification that menu cannot be opened.");
+ sOpenBlocked.set(true);
+ }},
+ new IntentFilter(INTENT_OPEN_BLOCKED),
null, null, Context.RECEIVER_EXPORTED);
}
@AfterClass
- public static void classTeardown() throws Throwable {
- clearPin();
+ public static void classTeardown() {
Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "");
}
@Before
public void setup() throws Throwable {
- clearPin();
+ sOpenBlocked.set(false);
wakeUpScreen();
sUiAutomation.executeShellCommand("input keyevent KEYCODE_MENU");
openMenu();
@@ -154,20 +161,8 @@
public void tearDown() throws Throwable {
closeMenu();
sLastGlobalAction.set(NO_GLOBAL_ACTION);
- }
-
- private static void clearPin() throws Throwable {
- sUiAutomation.executeShellCommand("locksettings clear --old " + TEST_PIN);
- TestUtils.waitUntil("Device did not register as unlocked & insecure.",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> !sKeyguardManager.isDeviceSecure());
- }
-
- private static void setPin() throws Throwable {
- sUiAutomation.executeShellCommand("locksettings set-pin " + TEST_PIN);
- TestUtils.waitUntil("Device did not recognize as locked & secure.",
- TIMEOUT_SERVICE_STATUS_CHANGE_S,
- () -> sKeyguardManager.isDeviceSecure());
+ // dismisses screenshot popup if present.
+ sUiAutomation.executeShellCommand("input keyevent KEYCODE_BACK");
}
private static boolean isMenuVisible() {
@@ -184,7 +179,6 @@
private static void closeScreen() throws Throwable {
Display display = sDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
- setPin();
sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN);
TestUtils.waitUntil("Screen did not close.",
TIMEOUT_UI_CHANGE_S,
@@ -194,12 +188,20 @@
}
private static void openMenu() throws Throwable {
- Intent intent = new Intent(PACKAGE_NAME + INTENT_TOGGLE_MENU);
+ openMenu(false);
+ }
+
+ private static void openMenu(boolean abandonOnBlock) throws Throwable {
+ Intent intent = new Intent(INTENT_TOGGLE_MENU);
+ intent.setPackage(PACKAGE_NAME);
sInstrumentation.getContext().sendBroadcast(intent);
TestUtils.waitUntil("Timed out before menu could appear.",
TIMEOUT_UI_CHANGE_S,
() -> {
+ if (sOpenBlocked.get() && abandonOnBlock) {
+ throw new IllegalStateException();
+ }
if (isMenuVisible()) {
return true;
} else {
@@ -213,7 +215,8 @@
if (!isMenuVisible()) {
return;
}
- Intent intent = new Intent(PACKAGE_NAME + INTENT_HIDE_MENU);
+ Intent intent = new Intent(INTENT_HIDE_MENU);
+ intent.setPackage(PACKAGE_NAME);
sInstrumentation.getContext().sendBroadcast(intent);
TestUtils.waitUntil("Timed out before menu could close.",
TIMEOUT_UI_CHANGE_S, () -> !isMenuVisible());
@@ -444,13 +447,13 @@
closeScreen();
wakeUpScreen();
- boolean timedOut = false;
+ boolean blocked = false;
try {
- openMenu();
- } catch (AssertionError e) {
+ openMenu(true);
+ } catch (IllegalStateException e) {
// Expected
- timedOut = true;
+ blocked = true;
}
- assertThat(timedOut).isTrue();
+ assertThat(blocked).isTrue();
}
}
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index d0e6b28..7bbe82c 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -4,26 +4,26 @@
name: "predictive_back_sysui"
namespace: "systemui"
description: "Predictive Back Dispatching for SysUI"
- bug: "309545085"
+ bug: "327737297"
}
flag {
name: "predictive_back_animate_shade"
namespace: "systemui"
description: "Enable Shade Animations"
- bug: "309545085"
+ bug: "327732946"
}
flag {
name: "predictive_back_animate_bouncer"
namespace: "systemui"
description: "Enable Predictive Back Animation in Bouncer"
- bug: "309545085"
+ bug: "327733487"
}
flag {
name: "predictive_back_animate_dialogs"
namespace: "systemui"
description: "Enable Predictive Back Animation for SysUI dialogs"
- bug: "309545085"
+ bug: "327721544"
}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8c8975f..d05d40d 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,16 @@
}
flag {
+ name: "notification_view_flipper_pausing"
+ namespace: "systemui"
+ description: "Pause ViewFlippers inside Notification custom layouts when the shade is closed."
+ bug: "309146176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notification_async_group_header_inflation"
namespace: "systemui"
description: "Inflates the notification group summary header views from the background thread."
@@ -194,14 +204,6 @@
}
flag {
- name: "keyguard_shade_migration_nssl"
- namespace: "systemui"
- description: "Moves NSSL into a shared element between the notification_panel and "
- "keyguard_root_view."
- bug: "278054201"
-}
-
-flag {
name: "unfold_animation_background_progress"
namespace: "systemui"
description: "Moves unfold animation progress calculation to a background thread"
@@ -419,6 +421,13 @@
}
flag {
+ name: "smartspace_remoteviews_rendering"
+ namespace: "systemui"
+ description: "Indicate Smartspace RemoteViews rendering"
+ bug: "326292691"
+}
+
+flag {
name: "pin_input_field_styled_focus_state"
namespace: "systemui"
description: "Enables styled focus states on pin input field if keyboard is connected"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 8dc7495..b124025 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,6 +27,7 @@
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
+import android.util.Log
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -140,7 +141,6 @@
}
sealed class PositionedGlyph {
-
/** Mutable X coordinate of the glyph position relative from drawing offset. */
var x: Float = 0f
@@ -269,41 +269,53 @@
duration: Long = -1L,
interpolator: TimeInterpolator? = null,
delay: Long = 0,
- onAnimationEnd: Runnable? = null
+ onAnimationEnd: Runnable? = null,
+ ) = setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = true)
+
+ private fun setTextStyleInternal(
+ fvar: String?,
+ textSize: Float,
+ color: Int?,
+ strokeWidth: Float,
+ animate: Boolean,
+ duration: Long,
+ interpolator: TimeInterpolator?,
+ delay: Long,
+ onAnimationEnd: Runnable?,
+ updateLayoutOnFailure: Boolean,
) {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
- }
+ try {
+ if (animate) {
+ animator.cancel()
+ textInterpolator.rebase()
+ }
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
+ if (textSize >= 0) {
+ textInterpolator.targetPaint.textSize = textSize
+ }
+ if (!fvar.isNullOrBlank()) {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+ }
+ if (color != null) {
+ textInterpolator.targetPaint.color = color
+ }
+ if (strokeWidth >= 0F) {
+ textInterpolator.targetPaint.strokeWidth = strokeWidth
+ }
+ textInterpolator.onTargetPaintModified()
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
-
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
- }
- textInterpolator.onTargetPaintModified()
-
- if (animate) {
- animator.startDelay = delay
- animator.duration =
- if (duration == -1L) {
- DEFAULT_ANIMATION_DURATION
- } else {
- duration
- }
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- val listener =
- object : AnimatorListenerAdapter() {
+ if (animate) {
+ animator.startDelay = delay
+ animator.duration =
+ if (duration == -1L) {
+ DEFAULT_ANIMATION_DURATION
+ } else {
+ duration
+ }
+ interpolator?.let { animator.interpolator = it }
+ if (onAnimationEnd != null) {
+ val listener = object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
onAnimationEnd.run()
animator.removeListener(this)
@@ -312,14 +324,25 @@
animator.removeListener(this)
}
}
- animator.addListener(listener)
+ animator.addListener(listener)
+ }
+ animator.start()
+ } else {
+ // No animation is requested, thus set base and target state to the same state.
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
+ } catch (ex: IllegalArgumentException) {
+ if (updateLayoutOnFailure) {
+ Log.e(TAG, "setTextStyleInternal: Exception caught but retrying. This is usually" +
+ " due to the layout having changed unexpectedly without being notified.", ex)
+ updateLayout(textInterpolator.layout)
+ setTextStyleInternal(fvar, textSize, color, strokeWidth, animate, duration,
+ interpolator, delay, onAnimationEnd, updateLayoutOnFailure = false)
+ } else {
+ throw ex
+ }
}
}
@@ -355,15 +378,13 @@
interpolator: TimeInterpolator? = null,
delay: Long = 0,
onAnimationEnd: Runnable? = null
- ) {
- val fvar = fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- )
- setTextStyle(
- fvar = fvar,
+ ) = setTextStyleInternal(
+ fvar = fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ ),
textSize = textSize,
color = color,
strokeWidth = strokeWidth,
@@ -372,6 +393,10 @@
interpolator = interpolator,
delay = delay,
onAnimationEnd = onAnimationEnd,
+ updateLayoutOnFailure = true,
)
+
+ companion object {
+ private val TAG = TextAnimator::class.simpleName!!
}
}
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
similarity index 67%
copy from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
copy to packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index 838e41e..a066b38 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,8 @@
* limitations under the License.
*/
-package android.hardware;
+package com.android.systemui.volume.panel.component.spatialaudio
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
+import dagger.Module
+@Module interface SpatialAudioModule
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
index bd539a7..2a13d49 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt
@@ -51,6 +51,7 @@
import androidx.compose.ui.unit.dp
import com.android.compose.PlatformIconButton
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
+import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection
import com.android.systemui.res.R
/** UI for the input part of a password-requiring version of the bouncer. */
@@ -71,6 +72,7 @@
val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState()
val animateFailure: Boolean by viewModel.animateFailure.collectAsState()
val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState()
+ val selectedUserId by viewModel.selectedUserId.collectAsState()
DisposableEffect(Unit) {
viewModel.onShown()
@@ -87,47 +89,51 @@
val color = MaterialTheme.colorScheme.onSurfaceVariant
val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() }
- TextField(
- value = password,
- onValueChange = viewModel::onPasswordInputChanged,
- enabled = isInputEnabled,
- visualTransformation = PasswordVisualTransformation(),
- singleLine = true,
- textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
- keyboardOptions =
- KeyboardOptions(
- keyboardType = KeyboardType.Password,
- imeAction = ImeAction.Done,
- ),
- keyboardActions =
- KeyboardActions(
- onDone = { viewModel.onAuthenticateKeyPressed() },
- ),
- modifier =
- modifier
- .focusRequester(focusRequester)
- .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
- .drawBehind {
- drawLine(
- color = color,
- start = Offset(x = 0f, y = size.height - lineWidthPx),
- end = Offset(size.width, y = size.height - lineWidthPx),
- strokeWidth = lineWidthPx,
- )
- }
- .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
- if (keyEvent.key == Key.Back) {
- viewModel.onImeDismissed()
- true
- } else {
- false
+ SelectedUserAwareInputConnection(selectedUserId) {
+ TextField(
+ value = password,
+ onValueChange = viewModel::onPasswordInputChanged,
+ enabled = isInputEnabled,
+ visualTransformation = PasswordVisualTransformation(),
+ singleLine = true,
+ textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
+ keyboardOptions =
+ KeyboardOptions(
+ keyboardType = KeyboardType.Password,
+ imeAction = ImeAction.Done,
+ ),
+ keyboardActions =
+ KeyboardActions(
+ onDone = { viewModel.onAuthenticateKeyPressed() },
+ ),
+ modifier =
+ modifier
+ .focusRequester(focusRequester)
+ .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) }
+ .drawBehind {
+ drawLine(
+ color = color,
+ start = Offset(x = 0f, y = size.height - lineWidthPx),
+ end = Offset(size.width, y = size.height - lineWidthPx),
+ strokeWidth = lineWidthPx,
+ )
}
- },
- trailingIcon =
- if (isImeSwitcherButtonVisible) {
- { ImeSwitcherButton(viewModel, color) }
- } else null
- )
+ .onInterceptKeyBeforeSoftKeyboard { keyEvent ->
+ if (keyEvent.key == Key.Back) {
+ viewModel.onImeDismissed()
+ true
+ } else {
+ false
+ }
+ },
+ trailingIcon =
+ if (isImeSwitcherButtonVisible) {
+ { ImeSwitcherButton(viewModel, color) }
+ } else {
+ null
+ }
+ )
+ }
}
/** Button for changing the password input method (IME). */
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
new file mode 100644
index 0000000..c8e1450
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalComposeUiApi::class)
+
+package com.android.systemui.common.ui.compose
+
+import android.annotation.UserIdInt
+import android.os.UserHandle
+import android.view.inputmethod.EditorInfo
+import android.view.inputmethod.InputConnection
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.platform.InterceptPlatformTextInput
+import androidx.compose.ui.platform.PlatformTextInputMethodRequest
+
+/**
+ * Wrapper for input connection composables that need to be aware of the selected user to connect to
+ * the correct instance of on-device services like autofill, autocorrect, etc.
+ *
+ * Usage:
+ * ```
+ * @Composable
+ * fun YourFunction(viewModel: YourViewModel) {
+ * val selectedUserId by viewModel.selectedUserId.collectAsState()
+ *
+ * SelectedUserAwareInputConnection(selectedUserId) {
+ * TextField(...)
+ * }
+ * }
+ * ```
+ */
+@Composable
+fun SelectedUserAwareInputConnection(
+ @UserIdInt selectedUserId: Int,
+ content: @Composable () -> Unit,
+) {
+ InterceptPlatformTextInput(
+ interceptor = { request, nextHandler ->
+ // Create a new request to wrap the incoming one with some custom logic.
+ val modifiedRequest =
+ object : PlatformTextInputMethodRequest {
+ override fun createInputConnection(outAttributes: EditorInfo): InputConnection {
+ val inputConnection = request.createInputConnection(outAttributes)
+ // After the original request finishes initializing the EditorInfo we can
+ // customize it. If we needed to we could also wrap the InputConnection
+ // before
+ // returning it.
+ updateEditorInfo(outAttributes)
+ return inputConnection
+ }
+
+ fun updateEditorInfo(outAttributes: EditorInfo) {
+ outAttributes.targetInputMethodUser = UserHandle.of(selectedUserId)
+ }
+ }
+
+ // Send our wrapping request to the next handler, which could be the system or another
+ // interceptor up the tree.
+ nextHandler.startInputMethod(modifiedRequest)
+ }
+ ) {
+ content()
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
index 0728daf..2a99039 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+import kotlinx.coroutines.flow.map
/**
* Modifies the composable to account for anti-burn in translation, alpha, and scaling.
@@ -38,9 +39,18 @@
params: BurnInParameters,
isClock: Boolean = false,
): Modifier {
- val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
- val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
- val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+ val burnIn = viewModel.movement(params)
+ val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f)
+ val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f)
+ val scaleViewModel by
+ burnIn
+ .map {
+ BurnInScaleViewModel(
+ scale = it.scale,
+ scaleClockOnly = it.scaleClockOnly,
+ )
+ }
+ .collectAsState(initial = BurnInScaleViewModel())
return this.graphicsLayer {
val scale =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
index b3fcc30..53de5bc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt
@@ -27,12 +27,14 @@
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.background
import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -137,12 +139,14 @@
}
}
) { targetViewModel ->
- Expandable(
- modifier = Modifier.fillMaxSize(),
- color = targetViewModel.backgroundColor.toColor(),
- shape = RoundedCornerShape(12.dp),
- onClick = { viewModel.onDeviceClick(it) },
- ) {}
+ Spacer(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(
+ color = targetViewModel.backgroundColor.toColor(),
+ shape = RoundedCornerShape(12.dp),
+ ),
+ )
}
transition.AnimatedContent(
contentKey = { it.icon },
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
new file mode 100644
index 0000000..ae267e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/selector/ui/composable/VolumePanelRadioButtons.kt
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.selector.ui.composable
+
+import androidx.compose.animation.core.animateOffsetAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+
+/**
+ * Radio button group for the Volume Panel. It allows selecting a single item
+ *
+ * @param indicatorBackgroundPadding is the distance between the edge of the indicator and the
+ * indicator background
+ * @param labelIndicatorBackgroundSpacing is the distance between indicator background and labels
+ * row
+ */
+@Composable
+fun VolumePanelRadioButtonBar(
+ modifier: Modifier = Modifier,
+ indicatorBackgroundPadding: Dp =
+ VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundPadding,
+ spacing: Dp = VolumePanelRadioButtonBarDefaults.DefaultSpacing,
+ labelIndicatorBackgroundSpacing: Dp =
+ VolumePanelRadioButtonBarDefaults.DefaultLabelIndicatorBackgroundSpacing,
+ indicatorCornerRadius: CornerRadius =
+ VolumePanelRadioButtonBarDefaults.defaultIndicatorCornerRadius(),
+ indicatorBackgroundCornerSize: CornerSize =
+ CornerSize(VolumePanelRadioButtonBarDefaults.DefaultIndicatorBackgroundCornerRadius),
+ colors: VolumePanelRadioButtonBarColors = VolumePanelRadioButtonBarDefaults.defaultColors(),
+ content: VolumePanelRadioButtonBarScope.() -> Unit
+) {
+ val scope =
+ VolumePanelRadioButtonBarScopeImpl().apply(content).apply {
+ require(hasSelectedItem) { "At least one item should be selected" }
+ }
+
+ val items = scope.items
+
+ var selectedIndex by remember { mutableIntStateOf(items.indexOfFirst { it.isSelected }) }
+
+ var size by remember { mutableStateOf(IntSize(0, 0)) }
+ val spacingPx = with(LocalDensity.current) { spacing.toPx() }
+ val indicatorWidth = size.width / items.size - (spacingPx * (items.size - 1) / items.size)
+ val offset by
+ animateOffsetAsState(
+ targetValue =
+ Offset(
+ selectedIndex * indicatorWidth + (spacingPx * selectedIndex),
+ 0f,
+ ),
+ label = "VolumePanelRadioButtonOffsetAnimation",
+ finishedListener = {
+ for (itemIndex in items.indices) {
+ val item = items[itemIndex]
+ if (itemIndex == selectedIndex) {
+ item.onItemSelected()
+ break
+ }
+ }
+ }
+ )
+
+ Column(modifier = modifier) {
+ Box(modifier = Modifier.height(IntrinsicSize.Max)) {
+ Canvas(
+ modifier =
+ Modifier.fillMaxSize()
+ .background(
+ colors.indicatorBackgroundColor,
+ RoundedCornerShape(indicatorBackgroundCornerSize),
+ )
+ .padding(indicatorBackgroundPadding)
+ .onGloballyPositioned { size = it.size }
+ ) {
+ drawRoundRect(
+ color = colors.indicatorColor,
+ topLeft = offset,
+ size = Size(indicatorWidth, size.height.toFloat()),
+ cornerRadius = indicatorCornerRadius,
+ )
+ }
+ Row(
+ modifier = Modifier.padding(indicatorBackgroundPadding),
+ horizontalArrangement = Arrangement.spacedBy(spacing)
+ ) {
+ for (itemIndex in items.indices) {
+ TextButton(
+ modifier = Modifier.weight(1f),
+ onClick = { selectedIndex = itemIndex },
+ ) {
+ val item = items[itemIndex]
+ if (item.icon !== Empty) {
+ with(items[itemIndex]) { icon() }
+ }
+ }
+ }
+ }
+ }
+
+ Row(
+ modifier =
+ Modifier.padding(
+ start = indicatorBackgroundPadding,
+ top = labelIndicatorBackgroundSpacing,
+ end = indicatorBackgroundPadding
+ ),
+ horizontalArrangement = Arrangement.spacedBy(spacing),
+ ) {
+ for (itemIndex in items.indices) {
+ TextButton(
+ modifier = Modifier.weight(1f),
+ onClick = { selectedIndex = itemIndex },
+ ) {
+ val item = items[itemIndex]
+ if (item.icon !== Empty) {
+ with(items[itemIndex]) { label() }
+ }
+ }
+ }
+ }
+ }
+}
+
+data class VolumePanelRadioButtonBarColors(
+ /** Color of the indicator. */
+ val indicatorColor: Color,
+ /** Color of the indicator background. */
+ val indicatorBackgroundColor: Color,
+)
+
+object VolumePanelRadioButtonBarDefaults {
+
+ val DefaultIndicatorBackgroundPadding = 8.dp
+ val DefaultSpacing = 24.dp
+ val DefaultLabelIndicatorBackgroundSpacing = 12.dp
+ val DefaultIndicatorCornerRadius = 20.dp
+ val DefaultIndicatorBackgroundCornerRadius = 20.dp
+
+ @Composable
+ fun defaultIndicatorCornerRadius(
+ x: Dp = DefaultIndicatorCornerRadius,
+ y: Dp = DefaultIndicatorCornerRadius,
+ ): CornerRadius = with(LocalDensity.current) { CornerRadius(x.toPx(), y.toPx()) }
+
+ /**
+ * Returns the default VolumePanelRadioButtonBar colors.
+ *
+ * @param indicatorColor is the color of the indicator
+ * @param indicatorBackgroundColor is the color of the indicator background
+ */
+ @Composable
+ fun defaultColors(
+ indicatorColor: Color = MaterialTheme.colorScheme.primaryContainer,
+ indicatorBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ ): VolumePanelRadioButtonBarColors =
+ VolumePanelRadioButtonBarColors(
+ indicatorColor = indicatorColor,
+ indicatorBackgroundColor = indicatorBackgroundColor,
+ )
+}
+
+/** [VolumePanelRadioButtonBar] content scope. Use [item] to add more items. */
+interface VolumePanelRadioButtonBarScope {
+
+ /**
+ * Adds a single item to the radio button group.
+ *
+ * @param isSelected true when the item is selected and false the otherwise
+ * @param onItemSelected is called when the item is selected
+ * @param icon of the to show in the indicator bar
+ * @param label to show below the indicator bar for the corresponding [icon]
+ */
+ fun item(
+ isSelected: Boolean,
+ onItemSelected: () -> Unit,
+ icon: @Composable RowScope.() -> Unit = Empty,
+ label: @Composable RowScope.() -> Unit = Empty,
+ )
+}
+
+private val Empty: @Composable RowScope.() -> Unit = {}
+
+private class VolumePanelRadioButtonBarScopeImpl : VolumePanelRadioButtonBarScope {
+
+ var hasSelectedItem: Boolean = false
+ private set
+
+ private val mutableItems: MutableList<Item> = mutableListOf()
+ val items: List<Item> = mutableItems
+
+ override fun item(
+ isSelected: Boolean,
+ onItemSelected: () -> Unit,
+ icon: @Composable RowScope.() -> Unit,
+ label: @Composable RowScope.() -> Unit,
+ ) {
+ require(!isSelected || !hasSelectedItem) { "Only one item should be selected at a time" }
+ hasSelectedItem = hasSelectedItem || isSelected
+ mutableItems.add(
+ Item(
+ isSelected = isSelected,
+ onItemSelected = onItemSelected,
+ icon = icon,
+ label = label,
+ )
+ )
+ }
+}
+
+private class Item(
+ val isSelected: Boolean,
+ val onItemSelected: () -> Unit,
+ val icon: @Composable RowScope.() -> Unit,
+ val label: @Composable RowScope.() -> Unit,
+)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
new file mode 100644
index 0000000..da29d58
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio
+
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
+import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+/** Dagger module, that provides Spatial Audio Volume Panel UI functionality. */
+@Module
+interface SpatialAudioModule {
+
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun bindComponentAvailabilityCriteria(
+ criteria: SpatialAudioAvailabilityCriteria
+ ): ComponentAvailabilityCriteria
+
+ companion object {
+
+ @Provides
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun provideVolumePanelUiComponent(
+ viewModel: SpatialAudioViewModel,
+ popup: SpatialAudioPopup,
+ ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
new file mode 100644
index 0000000..bed0ae8
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioPopup.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.toColor
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.volume.panel.component.popup.ui.composable.VolumePanelPopup
+import com.android.systemui.volume.panel.component.selector.ui.composable.VolumePanelRadioButtonBar
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import javax.inject.Inject
+
+class SpatialAudioPopup
+@Inject
+constructor(
+ private val viewModel: SpatialAudioViewModel,
+ private val volumePanelPopup: VolumePanelPopup,
+) {
+
+ /** Shows a popup with the [expandable] animation. */
+ fun show(expandable: Expandable) {
+ volumePanelPopup.show(expandable, { Title() }, { Content(it) })
+ }
+
+ @Composable
+ private fun Title() {
+ Text(
+ text = stringResource(R.string.volume_panel_spatial_audio_title),
+ style = MaterialTheme.typography.titleMedium,
+ textAlign = TextAlign.Center,
+ maxLines = 1,
+ )
+ }
+
+ @Composable
+ private fun Content(dialog: SystemUIDialog) {
+ val isAvailable by viewModel.isAvailable.collectAsState()
+
+ if (!isAvailable) {
+ SideEffect { dialog.dismiss() }
+ return
+ }
+
+ val enabledModelStates by viewModel.spatialAudioButtonByEnabled.collectAsState()
+ if (enabledModelStates.isEmpty()) {
+ return
+ }
+ VolumePanelRadioButtonBar {
+ for (buttonViewModel in enabledModelStates) {
+ item(
+ isSelected = buttonViewModel.button.isChecked,
+ onItemSelected = { viewModel.setEnabled(buttonViewModel.model) },
+ icon = {
+ Icon(
+ icon = buttonViewModel.button.icon,
+ tint = buttonViewModel.iconColor.toColor(),
+ )
+ },
+ label = {
+ Text(
+ text = buttonViewModel.button.label.toString(),
+ style = MaterialTheme.typography.labelMedium,
+ color = buttonViewModel.labelColor.toColor(),
+ )
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 92eb8f8..4950b96 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -45,32 +45,32 @@
import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
-import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor
-import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.phone.SystemUIDialogManager
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -102,6 +102,7 @@
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class UdfpsControllerOverlayTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
@JvmField @Rule var rule = MockitoJUnit.rule()
@@ -148,28 +149,11 @@
@Before
fun setup() {
- testScope = TestScope(StandardTestDispatcher())
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractor(
- powerRepository,
- mock(FalsingCollector::class.java),
- mock(ScreenOffAnimationController::class.java),
- statusBarStateController,
- )
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- keyguardTransitionInteractor =
- KeyguardTransitionInteractor(
- scope = testScope.backgroundScope,
- repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = {
- mock(FromLockscreenTransitionInteractor::class.java)
- },
- fromPrimaryBouncerTransitionInteractor = {
- mock(FromPrimaryBouncerTransitionInteractor::class.java)
- },
- fromAodTransitionInteractor = { mock(FromAodTransitionInteractor::class.java) },
- )
+ testScope = kosmos.testScope
+ powerRepository = kosmos.fakePowerRepository
+ powerInteractor = kosmos.powerInteractor
+ keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor
whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView)
whenever(inflater.inflate(R.layout.udfps_bp_view, null))
.thenReturn(mock(UdfpsBpView::class.java))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 37b135e..ce6445b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -23,7 +23,6 @@
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dock.DockManager
import com.android.systemui.dock.dockManager
import com.android.systemui.dock.fakeDockManager
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -40,6 +39,7 @@
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
@@ -92,6 +92,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun deviceDocked_forceCommunalScene() =
with(kosmos) {
@@ -110,6 +111,23 @@
}
@Test
+ fun exitingDream_forceCommunalScene() =
+ with(kosmos) {
+ testScope.runTest {
+ val scene by collectLastValue(communalInteractor.desiredScene)
+ assertThat(scene).isEqualTo(CommunalScenes.Blank)
+
+ updateDocked(true)
+ fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = this
+ )
+ assertThat(scene).isEqualTo(CommunalScenes.Communal)
+ }
+ }
+
+ @Test
fun deviceDocked_doesNotForceCommunalIfTransitioningFromCommunal() =
with(kosmos) {
testScope.runTest {
@@ -175,6 +193,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun dockingOnLockscreen_forcesCommunal() =
with(kosmos) {
@@ -196,6 +215,7 @@
}
}
+ @Ignore("Ignored until custom animations are implemented in b/322787129")
@Test
fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
with(kosmos) {
@@ -230,7 +250,8 @@
with(kosmos) {
runCurrent()
fakeDockManager.setIsDocked(docked)
- fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+ // TODO(b/322787129): uncomment once custom animations are in place
+ // fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 0a3aea7..723f6a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -27,6 +27,8 @@
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.wakelock.WakeLockFake
+import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -44,6 +46,9 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
+ private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
+ private lateinit var fakeWakeLock: WakeLockFake
+
@Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
@Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
@Mock private lateinit var activity: Activity
@@ -57,6 +62,10 @@
whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
.thenReturn(taskFragmentComponent)
+ fakeWakeLock = WakeLockFake()
+ fakeWakeLockBuilder = WakeLockFake.Builder(context)
+ fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
+
whenever(controlsComponent.getControlsListingController())
.thenReturn(Optional.of(controlsListingController))
@@ -87,12 +96,29 @@
verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
}
+ @Test
+ fun testAttachWindow_wakeLockAcquired() =
+ testScope.runTest {
+ underTest.onAttachedToWindow()
+ assertThat(fakeWakeLock.isHeld).isTrue()
+ }
+ @Test
+ fun testDetachWindow_wakeLockCanBeReleased() =
+ testScope.runTest {
+ underTest.onAttachedToWindow()
+ assertThat(fakeWakeLock.isHeld).isTrue()
+
+ underTest.onDetachedFromWindow()
+ assertThat(fakeWakeLock.isHeld).isFalse()
+ }
+
private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
with(kosmos) {
return HomeControlsDreamService(
controlsSettingsRepository = FakeControlsSettingsRepository(),
taskFragmentFactory = taskFragmentComponentFactory,
homeControlsComponentInteractor = homeControlsComponentInteractor,
+ fakeWakeLockBuilder,
dreamActivityProvider = activityProvider,
bgDispatcher = testDispatcher,
logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
index fb46ed9d..b3380ff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt
@@ -25,18 +25,17 @@
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -49,9 +48,10 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class LightRevealScrimRepositoryTest : SysuiTestCase() {
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
- private lateinit var powerInteractor: PowerInteractor
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+ private val powerInteractor = kosmos.powerInteractor
private lateinit var underTest: LightRevealScrimRepositoryImpl
@get:Rule val animatorTestRule = AnimatorTestRule(this)
@@ -59,13 +59,13 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- fakeKeyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
- powerInteractor =
- PowerInteractorFactory.create(repository = powerRepository).powerInteractor
-
underTest =
- LightRevealScrimRepositoryImpl(fakeKeyguardRepository, context, powerInteractor, mock())
+ LightRevealScrimRepositoryImpl(
+ kosmos.fakeKeyguardRepository,
+ context,
+ kosmos.powerInteractor,
+ mock()
+ )
}
@Test
@@ -168,8 +168,9 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo1AfterAnimationStarted() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(500L)
@@ -179,8 +180,9 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_startingRevealTwiceWontRerunAnimator() =
- runTest(UnconfinedTestDispatcher()) {
+ testScope.runTest {
val value by collectLastValue(underTest.revealAmount)
+ runCurrent()
underTest.startRevealAmountAnimator(true)
assertEquals(0.0f, value)
animatorTestRule.advanceTimeBy(250L)
@@ -193,12 +195,14 @@
@Test
@TestableLooper.RunWithLooper(setAsMainLooper = true)
fun revealAmount_emitsTo0AfterAnimationStartedReversed() =
- runTest(UnconfinedTestDispatcher()) {
- val value by collectLastValue(underTest.revealAmount)
- underTest.startRevealAmountAnimator(false)
- assertEquals(1.0f, value)
+ testScope.runTest {
+ val lastValue by collectLastValue(underTest.revealAmount)
+ runCurrent()
+ underTest.startRevealAmountAnimator(true)
animatorTestRule.advanceTimeBy(500L)
- assertEquals(0.0f, value)
+ underTest.startRevealAmountAnimator(false)
+ animatorTestRule.advanceTimeBy(500L)
+ assertEquals(0.0f, lastValue)
}
/**
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index f9ec3d1..24c651f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.domain.interactor
import android.app.StatusBarManager
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
@@ -120,6 +122,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testKeyguardGuardVisibilityStopsSecureCamera() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -144,6 +147,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testBouncerShowingResetsSecureCameraState() =
testScope.runTest {
val secureCameraActive = collectLastValue(underTest.isSecureCameraActive)
@@ -166,6 +170,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest {
val isVisible = collectLastValue(underTest.isKeyguardVisible)
repository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 63abc8f..0ebcf56 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -27,6 +27,7 @@
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.dock.DockManager
import com.android.systemui.dock.DockManagerFake
import com.android.systemui.flags.FakeFeatureFlags
@@ -49,6 +50,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
@@ -57,6 +59,7 @@
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -80,6 +83,7 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var launchAnimator: DialogTransitionAnimator
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
+ @Mock private lateinit var shadeInteractor: ShadeInteractor
@Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@@ -179,6 +183,7 @@
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = withDeps.keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -193,6 +198,8 @@
backgroundDispatcher = testDispatcher,
appContext = context,
)
+
+ whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
}
@Test
@@ -339,6 +346,25 @@
}
@Test
+ fun quickAffordance_updateOncePerShadeExpansion() =
+ testScope.runTest {
+ val shadeExpansion = MutableStateFlow(0f)
+ whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
+
+ val collectedValue by
+ collectValues(
+ underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+ )
+
+ val initialSize = collectedValue.size
+ for (i in 0..10) {
+ shadeExpansion.value = i / 10f
+ }
+
+ assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+ }
+
+ @Test
fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() =
testScope.runTest {
whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 225b5b1..de659cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -81,18 +81,18 @@
}
@Test
- fun translationY_initializedToZero() =
+ fun movement_initializedToZero() =
testScope.runTest {
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- assertThat(translationY).isEqualTo(0)
+ val movement by collectLastValue(underTest.movement(burnInParameters))
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(0f)
}
@Test
fun translationAndScale_whenNotDozing() =
testScope.runTest {
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to not dozing (on lockscreen)
keyguardTransitionRepository.sendTransitionStep(
@@ -113,24 +113,17 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
fun translationAndScale_whenFullyDozing() =
testScope.runTest {
burnInParameters = burnInParameters.copy(minViewY = 100)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -150,15 +143,10 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
- assertThat(translationY).isEqualTo(30)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(20)
+ assertThat(movement?.translationY).isEqualTo(30)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -170,15 +158,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -191,9 +174,7 @@
minViewY = 100,
topInset = 80,
)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -213,16 +194,11 @@
translationY = -30,
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
+ assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
- assertThat(translationY).isEqualTo(-20)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationY).isEqualTo(-20)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -234,15 +210,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -255,9 +226,7 @@
minViewY = 100,
topInset = 80,
)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -277,16 +246,11 @@
translationY = -30,
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(20)
+ assertThat(movement?.translationX).isEqualTo(20)
// -20 instead of -30, due to inset of 80
- assertThat(translationY).isEqualTo(-20)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 0.5f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationY).isEqualTo(-20)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
// Set to the beginning of GONE->AOD transition
keyguardTransitionRepository.sendTransitionStep(
@@ -298,15 +262,10 @@
),
validateStep = false,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale)
- .isEqualTo(
- BurnInScaleViewModel(
- scale = 1f,
- scaleClockOnly = true,
- )
- )
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(1f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(true)
}
@Test
@@ -314,9 +273,7 @@
testScope.runTest {
whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
- val translationX by collectLastValue(underTest.translationX(burnInParameters))
- val translationY by collectLastValue(underTest.translationY(burnInParameters))
- val scale by collectLastValue(underTest.scale(burnInParameters))
+ val movement by collectLastValue(underTest.movement(burnInParameters))
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -337,8 +294,9 @@
scale = 0.5f,
)
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+ assertThat(movement?.translationX).isEqualTo(0)
+ assertThat(movement?.translationY).isEqualTo(0)
+ assertThat(movement?.scale).isEqualTo(0.5f)
+ assertThat(movement?.scaleClockOnly).isEqualTo(false)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index ce089b1..04c270d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
new file mode 100644
index 0000000..c80eb13
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileDataInteractorTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileDataInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+ private val batteryController = FakeBatteryController(LeakCheck())
+ private val testUser = UserHandle.of(1)
+ private val underTest =
+ BatterySaverTileDataInteractor(testScope.testScheduler, batteryController)
+
+ @Test
+ fun availability_isTrue() =
+ testScope.runTest {
+ val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+ Truth.assertThat(availability).hasSize(1)
+ Truth.assertThat(availability.last()).isTrue()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerPowerSaving() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+
+ batteryController.setPowerSaveMode(true)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isTrue()
+
+ batteryController.setPowerSaveMode(false)
+ runCurrent()
+ Truth.assertThat(data!!.isPowerSaving).isFalse()
+ }
+
+ @Test
+ fun tileData_matchesBatteryControllerIsPluggedIn() =
+ testScope.runTest {
+ val data by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+
+ batteryController.isPluggedIn = true
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isTrue()
+
+ batteryController.isPluggedIn = false
+ runCurrent()
+ Truth.assertThat(data!!.isPluggedIn).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..62c51e6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/doman/interactor/BatterySaverTileUserActionInteractorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.doman.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileUserActionInteractorTest : SysuiTestCase() {
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+ private val controller = FakeBatteryController(LeakCheck())
+ private val underTest = BatterySaverTileUserActionInteractor(inputHandler, controller)
+
+ @Test
+ fun handleClickWhenNotPluggedIn_flipsPowerSaverMode() = runTest {
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = false
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(BatterySaverTileModel.Standard(false, originalPowerSaveMode))
+ )
+
+ Truth.assertThat(controller.isPowerSave).isNotEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleClickWhenPluggedIn_doesNotTurnOnPowerSaverMode() = runTest {
+ controller.setPowerSaveMode(false)
+ val originalPowerSaveMode = controller.isPowerSave
+ controller.isPluggedIn = true
+
+ underTest.handleInput(
+ QSTileInputTestKtx.click(
+ BatterySaverTileModel.Standard(controller.isPluggedIn, originalPowerSaveMode)
+ )
+ )
+
+ Truth.assertThat(controller.isPowerSave).isEqualTo(originalPowerSaveMode)
+ }
+
+ @Test
+ fun handleLongClick() = runTest {
+ underTest.handleInput(
+ QSTileInputTestKtx.longClick(BatterySaverTileModel.Standard(false, false))
+ )
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
new file mode 100644
index 0000000..6e9db2c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BatterySaverTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val batterySaverTileConfig = kosmos.qsBatterySaverTileConfig
+ private lateinit var mapper: BatterySaverTileMapper
+
+ @Before
+ fun setup() {
+ mapper =
+ BatterySaverTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.qs_battery_saver_icon_off, TestStubDrawable())
+ addOverride(R.drawable.qs_battery_saver_icon_on, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ )
+ }
+
+ @Test
+ fun map_standard_notPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_notPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_standard_pluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Standard(true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.standard_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.standard_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverDisabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, false)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.INACTIVE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledNotPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(false, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.extreme_battery_saver_text),
+ R.drawable.qs_battery_saver_icon_on,
+ context.getString(R.string.extreme_battery_saver_text),
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, true, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_on,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun map_extremeSaverEnabledPluggedInNotPowerSaving() {
+ val inputModel = BatterySaverTileModel.Extreme(true, false, true)
+
+ val outputState = mapper.map(batterySaverTileConfig, inputModel)
+
+ val expectedState =
+ createBatterySaverTileState(
+ QSTileState.ActivationState.UNAVAILABLE,
+ "",
+ R.drawable.qs_battery_saver_icon_off,
+ null,
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createBatterySaverTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ iconRes: Int,
+ stateDescription: CharSequence?,
+ ): QSTileState {
+ val label = context.getString(R.string.battery_detail_switch_title)
+ return QSTileState(
+ { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ label,
+ activationState,
+ secondaryLabel,
+ if (activationState == QSTileState.ActivationState.UNAVAILABLE)
+ setOf(QSTileState.UserAction.LONG_CLICK)
+ else setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ label,
+ stateDescription,
+ QSTileState.SideViewIcon.None,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
new file mode 100644
index 0000000..39755bf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileMapperTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val internetTileConfig = kosmos.qsInternetTileConfig
+ private val mapper by lazy {
+ InternetTileMapper(
+ context.orCreateTestableResources
+ .apply {
+ addOverride(R.drawable.ic_qs_no_internet_unavailable, TestStubDrawable())
+ addOverride(wifiRes, TestStubDrawable())
+ }
+ .resources,
+ context.theme,
+ context
+ )
+ }
+
+ @Test
+ fun withActiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Active(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_available),
+ iconId = wifiRes,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_internet_label),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.ACTIVE,
+ context.getString(R.string.quick_settings_networks_available),
+ Icon.Loaded(context.getDrawable(wifiRes)!!, contentDescription = null),
+ context.getString(R.string.quick_settings_internet_label)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ @Test
+ fun withInactiveModel_mappedStateMatchesDataModel() {
+ val inputModel =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ val outputState = mapper.map(internetTileConfig, inputModel)
+
+ val expectedState =
+ createInternetTileState(
+ QSTileState.ActivationState.INACTIVE,
+ context.getString(R.string.quick_settings_networks_unavailable),
+ Icon.Loaded(
+ context.getDrawable(R.drawable.ic_qs_no_internet_unavailable)!!,
+ contentDescription = null
+ ),
+ context.getString(R.string.quick_settings_networks_unavailable)
+ )
+ QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+ }
+
+ private fun createInternetTileState(
+ activationState: QSTileState.ActivationState,
+ secondaryLabel: String,
+ icon: Icon,
+ contentDescription: String,
+ ): QSTileState {
+ val label = context.getString(R.string.quick_settings_internet_label)
+ return QSTileState(
+ { icon },
+ label,
+ activationState,
+ secondaryLabel,
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+ contentDescription,
+ null,
+ QSTileState.SideViewIcon.Chevron,
+ QSTileState.EnabledState.ENABLED,
+ Switch::class.qualifiedName
+ )
+ }
+
+ private companion object {
+ val wifiRes = WIFI_FULL_ICONS[4]
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
new file mode 100644
index 0000000..37ef6ad
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractorTest.kt
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.os.UserHandle
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.AccessibilityContentDescriptions
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.connectivity.WifiIcons
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.util.CarrierConfigTracker
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InternetTileDataInteractorTest : SysuiTestCase() {
+ private val testUser = UserHandle.of(1)
+ private val kosmos = Kosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: InternetTileDataInteractor
+ private lateinit var mobileIconsInteractor: MobileIconsInteractor
+
+ private val airplaneModeRepository = FakeAirplaneModeRepository()
+ private val connectivityRepository = FakeConnectivityRepository()
+ private val ethernetInteractor = EthernetInteractor(connectivityRepository)
+ private val wifiRepository = FakeWifiRepository()
+ private val userSetupRepo = FakeUserSetupRepository()
+ private val wifiInteractor =
+ WifiInteractorImpl(connectivityRepository, wifiRepository, testScope.backgroundScope)
+
+ private val tableLogBuffer: TableLogBuffer = mock()
+ private val carrierConfigTracker: CarrierConfigTracker = mock()
+
+ private val mobileConnectionsRepository =
+ FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogBuffer)
+ private val mobileConnectionRepository =
+ FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
+
+ private val flags =
+ FakeFeatureFlagsClassic().also {
+ it.set(Flags.FILTER_PROVISIONING_NETWORK_SUBSCRIPTIONS, true)
+ }
+
+ private val internet = context.getString(R.string.quick_settings_internet_label)
+
+ @Before
+ fun setUp() {
+ mobileConnectionRepository.apply {
+ setNetworkTypeKey(mobileConnectionsRepository.GSM_KEY)
+ isInService.value = true
+ dataConnectionState.value = DataConnectionState.Connected
+ dataEnabled.value = true
+ }
+
+ mobileConnectionsRepository.apply {
+ activeMobileDataRepository.value = mobileConnectionRepository
+ activeMobileDataSubscriptionId.value = SUB_1_ID
+ setMobileConnectionRepositoryMap(mapOf(SUB_1_ID to mobileConnectionRepository))
+ }
+
+ mobileIconsInteractor =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogBuffer,
+ connectivityRepository,
+ userSetupRepo,
+ testScope.backgroundScope,
+ context,
+ flags,
+ )
+
+ context.orCreateTestableResources.apply {
+ addOverride(com.android.internal.R.drawable.ic_signal_cellular, TestStubDrawable())
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_0,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_1,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_2,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_3,
+ TestStubDrawable()
+ )
+ addOverride(
+ com.android.settingslib.R.drawable.ic_no_internet_wifi_signal_4,
+ TestStubDrawable()
+ )
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_phone, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_laptop, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_tablet, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_watch, TestStubDrawable())
+ addOverride(com.android.settingslib.R.drawable.ic_hotspot_auto, TestStubDrawable())
+ }
+
+ underTest =
+ InternetTileDataInteractor(
+ context,
+ testScope.backgroundScope,
+ airplaneModeRepository,
+ connectivityRepository,
+ ethernetInteractor,
+ mobileIconsInteractor,
+ wifiInteractor,
+ )
+ }
+
+ @Test
+ fun noDefault_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.defaultConnections.value = DefaultConnectionModel()
+
+ assertThat(latest?.secondaryLabel)
+ .isEqualTo(Text.Resource(R.string.quick_settings_networks_unavailable))
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_unavailable)
+ }
+
+ @Test
+ fun noDefault_networksAvailable() =
+ testScope.runTest {
+ // TODO(b/328419203): support [WifiInteractor.areNetworksAvailable]
+ }
+
+ @Test
+ fun wifiDefaultAndActive() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ )
+ val wifiIcon =
+ WifiIcon.fromModel(model = networkModel, context = context, showHotspotInfo = true)
+ as WifiIcon.Visible
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ assertThat(latest?.secondaryTitle).isEqualTo("test ssid")
+ assertThat(latest?.secondaryLabel).isNull()
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+
+ val actualIcon = latest?.icon
+ assertThat(actualIcon).isEqualTo(expectedIcon)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo("$internet,test ssid")
+ val expectedSd = wifiIcon.contentDescription
+ assertThat(latest?.stateDescription).isEqualTo(expectedSd)
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotNone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+
+ val expectedIcon =
+ Icon.Loaded(context.getDrawable(WifiIcons.WIFI_NO_INTERNET_ICONS[4])!!, null)
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .doesNotContain(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotTablet() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.TABLET)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_tablet)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotLaptop() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_laptop)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotWatch() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.WATCH)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_watch)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotAuto() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.AUTO)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_auto)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotPhone() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.PHONE)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotUnknown() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndActive_hotspotInvalid() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ setWifiNetworkWithHotspot(WifiNetworkModel.HotspotDeviceType.INVALID)
+
+ val expectedIcon =
+ Icon.Loaded(
+ context.getDrawable(com.android.settingslib.R.drawable.ic_hotspot_phone)!!,
+ null
+ )
+ assertThat(latest?.icon).isEqualTo(expectedIcon)
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(
+ context.getString(AccessibilityContentDescriptions.WIFI_OTHER_DEVICE_CONNECTION)
+ )
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_noNetworksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = emptyList()
+
+ assertThat(latest).isEqualTo(NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+ }
+
+ @Test
+ fun wifiDefaultAndNotActive_networksAvailable() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ val networkModel = WifiNetworkModel.Inactive
+
+ connectivityRepository.setWifiConnected(validated = false)
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ wifiRepository.wifiScanResults.value = listOf(WifiScanEntry("test 1"))
+
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.secondaryTitle)
+ .isEqualTo(context.getString(R.string.quick_settings_networks_available))
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.ic_qs_no_internet_available)
+ assertThat(latest?.stateDescription).isNull()
+ val expectedCd =
+ "$internet,${context.getString(R.string.quick_settings_networks_available)}"
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(expectedCd)
+ }
+
+ @Test
+ fun mobileDefault_usesNetworkNameAndIcon() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+
+ connectivityRepository.setMobileConnected()
+ mobileConnectionsRepository.mobileIsDefault.value = true
+ mobileConnectionRepository.apply {
+ setAllLevels(3)
+ setAllRoaming(false)
+ networkName.value = NetworkNameModel.Default("test network")
+ }
+
+ assertThat(latest).isNotNull()
+ assertThat(latest?.secondaryTitle).isNotNull()
+ assertThat(latest?.secondaryTitle.toString()).contains("test network")
+ assertThat(latest?.secondaryLabel).isNull()
+ assertThat(latest?.icon).isInstanceOf(Icon.Loaded::class.java)
+ assertThat(latest?.iconId).isNull()
+ assertThat(latest?.stateDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryTitle.toString())
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(internet)
+ }
+
+ @Test
+ fun ethernetDefault_validated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = true)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ @Test
+ fun ethernetDefault_notValidated_matchesInteractor() =
+ testScope.runTest {
+ val latest by
+ collectLastValue(
+ underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+ )
+ val ethernetIcon by collectLastValue(ethernetInteractor.icon)
+
+ connectivityRepository.setEthernetConnected(default = true, validated = false)
+
+ assertThat(latest?.secondaryLabel.loadText(context))
+ .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context))
+ assertThat(latest?.secondaryTitle).isNull()
+ assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet)
+ assertThat(latest?.icon).isNull()
+ assertThat(latest?.stateDescription).isNull()
+ assertThat(latest?.contentDescription.loadContentDescription(context))
+ .isEqualTo(latest?.secondaryLabel.loadText(context))
+ }
+
+ private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) {
+ val networkModel =
+ WifiNetworkModel.Active(
+ networkId = 1,
+ level = 4,
+ ssid = "test ssid",
+ hotspotDeviceType = hotspot,
+ )
+
+ connectivityRepository.setWifiConnected()
+ wifiRepository.setIsWifiDefault(true)
+ wifiRepository.setWifiNetwork(networkModel)
+ }
+
+ private companion object {
+ const val SUB_1_ID = 1
+
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..e1f3d97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractorTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class InternetTileUserActionInteractorTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+ private lateinit var underTest: InternetTileUserActionInteractor
+
+ @Mock private lateinit var internetDialogManager: InternetDialogManager
+ @Mock private lateinit var controller: AccessPointController
+
+ @Before
+ fun setup() {
+ internetDialogManager = mock<InternetDialogManager>()
+ controller = mock<AccessPointController>()
+
+ underTest =
+ InternetTileUserActionInteractor(
+ kosmos.testScope.coroutineContext,
+ internetDialogManager,
+ controller,
+ inputHandler,
+ )
+ }
+
+ @Test
+ fun handleClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.click(input))
+
+ verify(internetDialogManager).create(eq(true), anyBoolean(), anyBoolean(), nullable())
+ }
+
+ @Test
+ fun handleLongClickWhenActive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Active()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+
+ @Test
+ fun handleLongClickWhenInactive() =
+ kosmos.testScope.runTest {
+ val input = InternetTileModel.Inactive()
+
+ underTest.handleInput(QSTileInputTestKtx.longClick(input))
+
+ QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+ Truth.assertThat(it.intent.action).isEqualTo(Settings.ACTION_WIFI_SETTINGS)
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index d505b27..7fabe33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS;
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
import static android.app.Flags.FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS;
+import static android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.UserHandle.USER_ALL;
import static android.provider.Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
@@ -115,7 +116,8 @@
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
FLAG_ALLOW_PRIVATE_PROFILE,
- FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS);
+ FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS,
+ FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
}
public NotificationLockscreenUserManagerTest(FlagsParameterization flags) {
@@ -872,7 +874,7 @@
}
@Test
- @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testProfileAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -883,7 +885,7 @@
}
@Test
- @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @EnableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testProfileUnAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size());
@@ -894,7 +896,7 @@
}
@Test
- @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testManagedProfileAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
mLockscreenUserManager.mCurrentManagedProfiles.clear();
@@ -908,7 +910,7 @@
}
@Test
- @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE)
+ @DisableFlags({FLAG_ALLOW_PRIVATE_PROFILE, FLAG_ENABLE_PRIVATE_SPACE_FEATURES})
public void testManagedProfileUnAvailabilityIntent() {
mLockscreenUserManager.mCurrentProfiles.clear();
mLockscreenUserManager.mCurrentManagedProfiles.clear();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
index e188f5b..8e765f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/NotificationsSoundPolicyInteractorTest.kt
@@ -196,10 +196,14 @@
}
@Test
- fun zenModeAlarms_ringAndNotifications_muted() {
+ fun zenModeAlarms_ringedStreams_muted() {
with(kosmos) {
val expectedToBeMuted =
- setOf(AudioManager.STREAM_RING, AudioManager.STREAM_NOTIFICATION)
+ setOf(
+ AudioManager.STREAM_RING,
+ AudioManager.STREAM_NOTIFICATION,
+ AudioManager.STREAM_SYSTEM,
+ )
testScope.runTest {
notificationsSoundPolicyRepository.updateNotificationPolicy()
notificationsSoundPolicyRepository.updateZenMode(ZenMode(Global.ZEN_MODE_ALARMS))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 0de15b8..deb1976 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -34,6 +34,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -66,7 +67,7 @@
@RunWith(AndroidJUnit4::class)
class SharedNotificationContainerViewModelTest : SysuiTestCase() {
val aodBurnInViewModel = mock(AodBurnInViewModel::class.java)
- lateinit var translationYFlow: MutableStateFlow<Float>
+ lateinit var movementFlow: MutableStateFlow<BurnInModel>
val kosmos =
testKosmos().apply {
@@ -95,8 +96,8 @@
@Before
fun setUp() {
overrideResource(R.bool.config_use_split_notification_shade, false)
- translationYFlow = MutableStateFlow(0f)
- whenever(aodBurnInViewModel.translationY(any())).thenReturn(translationYFlow)
+ movementFlow = MutableStateFlow(BurnInModel())
+ whenever(aodBurnInViewModel.movement(any())).thenReturn(movementFlow)
underTest = kosmos.sharedNotificationContainerViewModel
}
@@ -608,7 +609,7 @@
showLockscreen()
assertThat(translationY).isEqualTo(0)
- translationYFlow.value = 150f
+ movementFlow.value = BurnInModel(translationY = 150)
assertThat(translationY).isEqualTo(150f)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 243aab2..dcf635e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -32,8 +32,8 @@
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaController
import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.mediaOutputActionsInteractor
import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.mediaOutputActionsInteractor
import com.android.systemui.volume.panel.volumePanelViewModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
index 36be90e..449e8bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt
@@ -78,7 +78,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultHeadTrackingAvailable = false
spatializerRepository.defaultSpatialAudioAvailable = false
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -94,7 +94,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(false)
+ spatializerRepository.defaultHeadTrackingAvailable = false
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -110,7 +110,7 @@
with(kosmos) {
testScope.runTest {
localMediaRepository.updateCurrentConnectedDevice(bluetoothMediaDevice)
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
@@ -125,7 +125,7 @@
fun spatialAudio_headTracking_noDevice_unavailable() {
with(kosmos) {
testScope.runTest {
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
spatializerRepository.defaultSpatialAudioAvailable = true
val isAvailable by collectLastValue(underTest.isAvailable())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
index eb6f0b2..06ae220 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt
@@ -82,7 +82,7 @@
),
true
)
- spatializerRepository.setIsHeadTrackingAvailable(true)
+ spatializerRepository.defaultHeadTrackingAvailable = true
underTest =
SpatialAudioComponentInteractor(
diff --git a/packages/SystemUI/res/drawable/ic_head_tracking.xml b/packages/SystemUI/res/drawable/ic_head_tracking.xml
new file mode 100644
index 0000000..d4a44fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_head_tracking.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,520Q414,520 367,473Q320,426 320,360Q320,294 367,247Q414,200 480,200Q546,200 593,247Q640,294 640,360Q640,426 593,473Q546,520 480,520ZM160,840L160,728Q160,695 177,666Q194,637 224,622Q275,596 339,578Q403,560 480,560Q557,560 621,578Q685,596 736,622Q766,637 783,666Q800,695 800,728L800,840L160,840ZM240,760L720,760L720,728Q720,717 714.5,708Q709,699 700,694Q664,676 607.5,658Q551,640 480,640Q409,640 352.5,658Q296,676 260,694Q251,699 245.5,708Q240,717 240,728L240,760ZM480,440Q513,440 536.5,416.5Q560,393 560,360Q560,327 536.5,303.5Q513,280 480,280Q447,280 423.5,303.5Q400,327 400,360Q400,393 423.5,416.5Q447,440 480,440ZM39,200L39,120Q56,120 70,113.5Q84,107 95,96Q106,85 112,71Q118,57 118,40L199,40Q199,73 186.5,102Q174,131 152,153Q130,175 101,187.5Q72,200 39,200ZM39,361L39,281Q90,281 133.5,262Q177,243 209,210Q241,177 260,133.5Q279,90 279,40L360,40Q360,106 335,164.5Q310,223 266,267Q222,311 164,336Q106,361 39,361ZM920,361Q854,361 795.5,336Q737,311 693,267Q649,223 624,164.5Q599,106 599,40L679,40Q679,90 698,133.5Q717,177 750,210Q783,243 826.5,262Q870,281 920,281L920,361ZM920,200Q887,200 858,187.5Q829,175 807,153Q785,131 772.5,102Q760,73 760,40L840,40Q840,57 846.5,71Q853,85 864,96Q875,107 889,113.5Q903,120 920,120L920,200ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360ZM480,760L480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760Q480,760 480,760L480,760L480,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio.xml b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
new file mode 100644
index 0000000..0ee609a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M920,401Q848,401 782,373.5Q716,346 665,295Q614,244 586.5,178Q559,112 559,40L639,40Q639,97 660,148Q681,199 721,239Q761,279 812,300.5Q863,322 920,322L920,401ZM920,242Q879,242 842.5,227Q806,212 777,183Q748,154 733,117.5Q718,81 718,40L797,40Q797,65 806.5,87.5Q816,110 833,127Q850,144 872.5,153Q895,162 920,162L920,242ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
new file mode 100644
index 0000000..c7d3272
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_spatial_audio_off.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M750,550L806,494Q766,454 743.5,402.5Q721,351 721,294Q721,237 743.5,186Q766,135 806,95L750,37Q699,88 670,155Q641,222 641,294Q641,366 670,432.5Q699,499 750,550ZM862,436L918,380Q901,363 891,341Q881,319 881,294Q881,269 891,247Q901,225 918,208L862,151Q833,180 817,216Q801,252 801,293Q801,334 817,371Q833,408 862,436ZM400,520Q334,520 287,473Q240,426 240,360Q240,294 287,247Q334,200 400,200Q466,200 513,247Q560,294 560,360Q560,426 513,473Q466,520 400,520ZM80,840L80,728Q80,695 97,666Q114,637 144,622Q195,596 259,578Q323,560 400,560Q477,560 541,578Q605,596 656,622Q686,637 703,666Q720,695 720,728L720,840L80,840ZM160,760L640,760L640,728Q640,717 634.5,708Q629,699 620,694Q584,676 527.5,658Q471,640 400,640Q329,640 272.5,658Q216,676 180,694Q171,699 165.5,708Q160,717 160,728L160,760ZM400,440Q433,440 456.5,416.5Q480,393 480,360Q480,327 456.5,303.5Q433,280 400,280Q367,280 343.5,303.5Q320,327 320,360Q320,393 343.5,416.5Q367,440 400,440ZM400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360Q400,360 400,360ZM400,760L400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760Q400,760 400,760L400,760L400,760Z" />
+</vector>
diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
index 61f69c0..f6042e4 100644
--- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
+++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml
@@ -128,7 +128,8 @@
android:layout_marginStart="49dp"
android:layout_marginEnd="49dp"
android:overScrollMode="never"
- android:layout_marginBottom="16dp">
+ android:layout_marginBottom="16dp"
+ android:scrollbars="none">
<LinearLayout
android:id="@+id/keyboard_shortcuts_container"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/notif_half_shelf.xml b/packages/SystemUI/res/layout/notif_half_shelf.xml
index 68c8dd9..d8d2985 100644
--- a/packages/SystemUI/res/layout/notif_half_shelf.xml
+++ b/packages/SystemUI/res/layout/notif_half_shelf.xml
@@ -19,11 +19,11 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/half_shelf_dialog"
android:orientation="vertical"
- android:layout_width="wrap_content"
+ android:layout_width="@dimen/large_dialog_width"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom"
- android:paddingStart="4dp"
- android:paddingEnd="4dp">
+ android:paddingLeft="@dimen/dialog_side_padding"
+ android:paddingRight="@dimen/dialog_side_padding">
<LinearLayout
android:id="@+id/half_shelf"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e181d07..35f6a08 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -345,9 +345,6 @@
the notification is not swiped enough to dismiss it. -->
<bool name="config_showNotificationGear">true</bool>
- <!-- Whether or not a background should be drawn behind a notification. -->
- <bool name="config_drawNotificationBackground">false</bool>
-
<!-- Whether or the notifications can be shown and dismissed with a drag. -->
<bool name="config_enableNotificationShadeDrag">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b713417..f71c415 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -75,6 +75,11 @@
<!-- Battery saver notification dismiss action: Do not turn on battery saver. [CHAR LIMIT=NONE]-->
<string name="battery_saver_dismiss_action">No thanks</string>
+ <!-- Secondary label for Battery Saver tile when Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="standard_battery_saver_text">Standard</string>
+ <!-- Secondary label for Battery Saver tile when Extreme Battery Saver is enabled. [CHAR LIMIT=20] -->
+ <string name="extreme_battery_saver_text">Extreme</string>
+
<!-- Name of the button that links to the Settings app. [CHAR LIMIT=NONE] -->
<!-- Name of the button that links to the Wifi settings screen. [CHAR LIMIT=NONE] -->
@@ -1533,8 +1538,16 @@
<string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
<string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
- <!-- Label for button to enabled/disable live caption [CHAR_LIMIT=30] -->
+ <!-- Label for button to enabled/disable active noise cancellation [CHAR_LIMIT=30] -->
<string name="volume_panel_noise_control_title">Noise Control</string>
+ <!-- Label for button to enabled/disable spatial audio [CHAR_LIMIT=30] -->
+ <string name="volume_panel_spatial_audio_title">Spatial Audio</string>
+ <!-- Label for button to disable spatial audio [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_off">Off</string>
+ <!-- Label for button to enabled spatial audio [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_fixed">Fixed</string>
+ <!-- Label for button to enabled head tracking [CHAR_LIMIT=20] -->
+ <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
<string name="volume_ringer_change">Tap to change ringer mode</string>
diff --git a/packages/SystemUI/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml
index b67378e..71cc05d 100644
--- a/packages/SystemUI/res/xml/fileprovider.xml
+++ b/packages/SystemUI/res/xml/fileprovider.xml
@@ -19,4 +19,5 @@
<cache-path name="leak" path="leak/"/>
<external-path name="screenrecord" path="."/>
<cache-path name="multi_user" path="multi_user/" />
-</paths>
\ No newline at end of file
+ <root-path name="traces" path="/data/local/traces"/>
+</paths>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f28d405..8a2245d 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -404,7 +404,9 @@
if (nextAlarmMillis > 0) nextAlarmMillis else null,
SysuiR.string::status_bar_alarm.name
)
- .also { data -> clock?.run { events.onAlarmDataChanged(data) } }
+ .also { data ->
+ mainExecutor.execute { clock?.run { events.onAlarmDataChanged(data) } }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 77054bd..f5a6cb3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -89,7 +89,7 @@
boolean goingToFullShade,
int oldStatusBarState) {
if (migrateClocksToBlueprint()) {
- log("Ignoring all of KeyguardVisibilityelper");
+ log("Ignoring KeyguardVisibilityelper, migrateClocksToBlueprint flag on");
return;
}
Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 8853589..33f14d4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -22,17 +22,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.Preconditions;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
-import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -47,7 +43,6 @@
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -58,7 +53,6 @@
import com.android.systemui.statusbar.phone.SystemUIDialogManager;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
@@ -70,6 +64,7 @@
import javax.inject.Inject;
import javax.inject.Named;
+
/**
* Class to handle ugly dependencies throughout sysui until we determine the
* long-term dependency injection solution.
@@ -96,10 +91,6 @@
* Key for getting a Handler for receiving time tick broadcasts on.
*/
public static final String TIME_TICK_HANDLER_NAME = "time_tick_handler";
- /**
- * Generic handler on the main thread.
- */
- private static final String MAIN_HANDLER_NAME = "main_handler";
/**
* An email address to send memory leak reports to by default.
@@ -121,11 +112,6 @@
*/
public static final DependencyKey<Handler> TIME_TICK_HANDLER =
new DependencyKey<>(TIME_TICK_HANDLER_NAME);
- /**
- * Generic handler on the main thread.
- */
- public static final DependencyKey<Handler> MAIN_HANDLER =
- new DependencyKey<>(MAIN_HANDLER_NAME);
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
@@ -134,7 +120,6 @@
@Inject Lazy<BroadcastDispatcher> mBroadcastDispatcher;
@Inject Lazy<BluetoothController> mBluetoothController;
- @Inject Lazy<FlashlightController> mFlashlightController;
@Inject Lazy<KeyguardUpdateMonitor> mKeyguardUpdateMonitor;
@Inject Lazy<DeviceProvisionedController> mDeviceProvisionedController;
@Inject Lazy<PluginManager> mPluginManager;
@@ -150,15 +135,10 @@
@Inject Lazy<LightBarController> mLightBarController;
@Inject Lazy<OverviewProxyService> mOverviewProxyService;
@Inject Lazy<NavigationModeController> mNavBarModeController;
- @Inject Lazy<AccessibilityButtonModeObserver> mAccessibilityButtonModeObserver;
- @Inject Lazy<AccessibilityButtonTargetsObserver> mAccessibilityButtonListController;
- @Inject Lazy<IStatusBarService> mIStatusBarService;
- @Inject Lazy<NotificationRemoteInputManager.Callback> mNotificationRemoteInputManagerCallback;
@Inject Lazy<NavigationBarController> mNavigationBarController;
@Inject Lazy<StatusBarStateController> mStatusBarStateController;
@Inject Lazy<NotificationMediaManager> mNotificationMediaManager;
@Inject @Background Lazy<Looper> mBgLooper;
- @Inject @Main Lazy<Handler> mMainHandler;
@Inject @Named(TIME_TICK_HANDLER_NAME) Lazy<Handler> mTimeTickHandler;
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<CommandQueue> mCommandQueue;
@@ -187,10 +167,8 @@
// on imports.
mProviders.put(TIME_TICK_HANDLER, mTimeTickHandler::get);
mProviders.put(BG_LOOPER, mBgLooper::get);
- mProviders.put(MAIN_HANDLER, mMainHandler::get);
mProviders.put(BroadcastDispatcher.class, mBroadcastDispatcher::get);
mProviders.put(BluetoothController.class, mBluetoothController::get);
- mProviders.put(FlashlightController.class, mFlashlightController::get);
mProviders.put(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor::get);
mProviders.put(DeviceProvisionedController.class, mDeviceProvisionedController::get);
mProviders.put(PluginManager.class, mPluginManager::get);
@@ -205,13 +183,6 @@
mProviders.put(LightBarController.class, mLightBarController::get);
mProviders.put(OverviewProxyService.class, mOverviewProxyService::get);
mProviders.put(NavigationModeController.class, mNavBarModeController::get);
- mProviders.put(AccessibilityButtonModeObserver.class,
- mAccessibilityButtonModeObserver::get);
- mProviders.put(AccessibilityButtonTargetsObserver.class,
- mAccessibilityButtonListController::get);
- mProviders.put(IStatusBarService.class, mIStatusBarService::get);
- mProviders.put(NotificationRemoteInputManager.Callback.class,
- mNotificationRemoteInputManagerCallback::get);
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
mProviders.put(StatusBarStateController.class, mStatusBarStateController::get);
mProviders.put(NotificationMediaManager.class, mNotificationMediaManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 31698a3..01c2cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -345,11 +345,25 @@
}
}
- private TextView loadPercentView() {
+ private TextView inflatePercentView() {
return (TextView) LayoutInflater.from(getContext())
.inflate(R.layout.battery_percentage_view, null);
}
+ private void addPercentView(TextView inflatedPercentView) {
+ mBatteryPercentView = inflatedPercentView;
+
+ if (mPercentageStyleId != 0) { // Only set if specified as attribute
+ mBatteryPercentView.setTextAppearance(mPercentageStyleId);
+ }
+ float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
+ mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
+ if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addView(mBatteryPercentView, new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ (int) Math.ceil(fontHeight)));
+ }
+
/**
* Updates percent view by removing old one and reinflating if necessary
*/
@@ -388,7 +402,9 @@
mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
(String estimate) -> {
if (mBatteryPercentView == null) {
- mBatteryPercentView = loadPercentView();
+ // Similar to the legacy behavior, inflate and add the view. We will
+ // only use it for the estimate text
+ addPercentView(inflatePercentView());
}
if (estimate != null && mShowPercentMode == MODE_ESTIMATE) {
mEstimateText = estimate;
@@ -401,6 +417,10 @@
}
});
} else {
+ if (mBatteryPercentView != null) {
+ mEstimateText = null;
+ mBatteryPercentView.setText(null);
+ }
updateContentDescription();
}
}
@@ -485,22 +505,19 @@
return;
}
- if (mUnifiedBattery == null) {
- return;
+ if (!mShowPercentAvailable || mUnifiedBattery == null) return;
+
+ boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE;
+ if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) {
+ // Slow case: fall back to the system setting
+ // TODO(b/140051051)
+ shouldShow = 0 != whitelistIpcs(() -> Settings.System
+ .getIntForUser(getContext().getContentResolver(),
+ SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
+ ? 1 : 0, UserHandle.USER_CURRENT));
}
- // TODO(b/140051051)
- final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
- .getIntForUser(getContext().getContentResolver(),
- SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean(
- com.android.internal.R.bool.config_defaultBatteryPercentageSetting)
- ? 1 : 0, UserHandle.USER_CURRENT));
-
- boolean shouldShow =
- (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON;
- shouldShow = shouldShow && !mBatteryStateUnknown;
-
setBatteryDrawableState(
new BatteryDrawableState(
mUnifiedBatteryState.getLevel(),
@@ -534,17 +551,8 @@
if (shouldShow) {
if (!showing) {
- mBatteryPercentView = loadPercentView();
- if (mPercentageStyleId != 0) { // Only set if specified as attribute
- mBatteryPercentView.setTextAppearance(mPercentageStyleId);
- }
- float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null);
- mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight);
- if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
+ addPercentView(inflatePercentView());
updatePercentText();
- addView(mBatteryPercentView, new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- (int) Math.ceil(fontHeight)));
}
} else {
if (showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
index 8c9ae62..10a0d95 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatterySaverModule.kt
@@ -1,9 +1,21 @@
package com.android.systemui.battery
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.BatterySaverTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.interactor.BatterySaverTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.impl.battery.ui.BatterySaverTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
import dagger.Binds
import dagger.Module
+import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -15,4 +27,39 @@
@IntoMap
@StringKey(BatterySaverTile.TILE_SPEC)
fun bindBatterySaverTile(batterySaverTile: BatterySaverTile): QSTileImpl<*>
+
+ companion object {
+ private const val BATTERY_SAVER_TILE_SPEC = "battery"
+
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.qs_battery_saver_icon_off,
+ labelRes = R.string.battery_detail_switch_title,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject BatterySaverTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(BATTERY_SAVER_TILE_SPEC)
+ fun provideBatterySaverTileViewModel(
+ factory: QSTileViewModelFactory.Static<BatterySaverTileModel>,
+ mapper: BatterySaverTileMapper,
+ stateInteractor: BatterySaverTileDataInteractor,
+ userActionInteractor: BatterySaverTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(BATTERY_SAVER_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
index 1b8495a..f3652b8 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryAttributionDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.drawable.Drawable
import android.graphics.drawable.DrawableWrapper
import android.view.Gravity
+import kotlin.math.ceil
import kotlin.math.min
import kotlin.math.roundToInt
@@ -36,7 +37,7 @@
*/
@Suppress("RtlHardcoded")
class BatteryAttributionDrawable(dr: Drawable?) : DrawableWrapper(dr) {
- /** One of [CENTER, LEFT]. Note that RTL is handled in the parent */
+ /** One of [CENTER, LEFT]. Note that number text does not RTL. */
var gravity = Gravity.CENTER
set(value) {
field = value
@@ -67,8 +68,8 @@
dr.setBounds(
bounds.left,
bounds.top,
- (bounds.left + dw).roundToInt(),
- (bounds.top + dh).roundToInt()
+ ceil(bounds.left + dw).toInt(),
+ ceil(bounds.top + dh).toInt()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
index 6d32067..5e34d29 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryFillDrawable.kt
@@ -26,6 +26,7 @@
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
import kotlin.math.floor
import kotlin.math.roundToInt
@@ -103,6 +104,11 @@
// saveLayer is needed here so we don't clip the other layers of our drawable
canvas.saveLayer(null, null)
+ // Fill from the opposite direction in rtl mode
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL) {
+ canvas.scale(-1f, 1f, bounds.width() / 2f, bounds.height() / 2f)
+ }
+
// We need to use 3 draw commands:
// 1. Clip to the current level
// 2. Clip anything outside of the path
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
index 199dd1f..706b9ec 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryLayersDrawable.kt
@@ -26,7 +26,10 @@
import android.graphics.drawable.LayerDrawable
import android.util.PathParser
import android.view.Gravity
+import android.view.View
import com.android.systemui.res.R
+import kotlin.math.ceil
+import kotlin.math.floor
import kotlin.math.roundToInt
/**
@@ -69,8 +72,11 @@
) : LayerDrawable(arrayOf(frameBg, frame, fill, textOnly, spaceSharingText, attribution)) {
private val scaleMatrix = Matrix().also { it.setScale(1f, 1f) }
- private val scaledAttrFullCanvas = RectF(Metrics.AttrFullCanvas)
- private val scaledAttrRightCanvas = RectF(Metrics.AttrRightCanvas)
+
+ private val attrFullCanvas = RectF()
+ private val attrRightCanvas = RectF()
+ private val scaledAttrFullCanvas = RectF()
+ private val scaledAttrRightCanvas = RectF()
var batteryState = batteryState
set(value) {
@@ -88,6 +94,12 @@
updateColors(batteryState.showErrorState, value)
}
+ init {
+ isAutoMirrored = true
+ // Initialize the canvas rects since they are not static
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ }
+
private fun handleUpdateState(old: BatteryDrawableState, new: BatteryDrawableState) {
if (new.showErrorState != old.showErrorState) {
updateColors(new.showErrorState, colors)
@@ -144,9 +156,42 @@
bounds.height() / Metrics.ViewportHeight
)
- // Scale the attribution bounds
- scaleMatrix.mapRect(scaledAttrFullCanvas, Metrics.AttrFullCanvas)
- scaleMatrix.mapRect(scaledAttrRightCanvas, Metrics.AttrRightCanvas)
+ scaleAttributionBounds()
+ }
+
+ override fun onLayoutDirectionChanged(layoutDirection: Int): Boolean {
+ setAttrRects(layoutDirection == View.LAYOUT_DIRECTION_RTL)
+ scaleAttributionBounds()
+
+ return super.onLayoutDirectionChanged(layoutDirection)
+ }
+
+ private fun setAttrRects(rtl: Boolean) {
+ // Local refs make the math easier to parse
+ val full = Metrics.AttrFullCanvasInsets
+ val side = Metrics.AttrRightCanvasInsets
+ val sideRtl = Metrics.AttrRightCanvasInsetsRtl
+ val vh = Metrics.ViewportHeight
+ val vw = Metrics.ViewportWidth
+
+ attrFullCanvas.set(
+ if (rtl) full.right else full.left,
+ full.top,
+ vw - if (rtl) full.left else full.right,
+ vh - full.bottom,
+ )
+ attrRightCanvas.set(
+ if (rtl) sideRtl.left else side.left,
+ side.top,
+ vw - (if (rtl) sideRtl.right else side.right),
+ vh - side.bottom,
+ )
+ }
+
+ /** If bounds (i.e., scale), or RTL properties change, we have to recalculate the attr bounds */
+ private fun scaleAttributionBounds() {
+ scaleMatrix.mapRect(scaledAttrFullCanvas, attrFullCanvas)
+ scaleMatrix.mapRect(scaledAttrRightCanvas, attrRightCanvas)
}
override fun draw(canvas: Canvas) {
@@ -163,13 +208,14 @@
if (batteryState.showPercent && batteryState.attribution != null) {
// 4a. percent & attribution. Implies space-sharing
- // Configure the attribute to draw in a smaller bounding box and align left
+ // Configure the attribute to draw in a smaller bounding box and align left and use
+ // floor/ceil math to make sure we get every available pixel
attribution.gravity = Gravity.LEFT
attribution.setBounds(
- scaledAttrRightCanvas.left.roundToInt(),
- scaledAttrRightCanvas.top.roundToInt(),
- scaledAttrRightCanvas.right.roundToInt(),
- scaledAttrRightCanvas.bottom.roundToInt(),
+ floor(scaledAttrRightCanvas.left).toInt(),
+ floor(scaledAttrRightCanvas.top).toInt(),
+ ceil(scaledAttrRightCanvas.right).toInt(),
+ ceil(scaledAttrRightCanvas.bottom).toInt(),
)
attribution.draw(canvas)
@@ -196,16 +242,44 @@
*/
override fun setAlpha(alpha: Int) {}
+ /**
+ * Interface that describes relevant top-level metrics for the proper rendering of this icon.
+ * The overall canvas is defined as ViewportWidth x ViewportHeight, which is hard coded to 24x14
+ * points.
+ *
+ * The attr canvas insets are rect inset definitions. That is, they are defined as l,t,r,b
+ * points from the nearest edge. Note that for RTL, we don't actually flip the text since
+ * numbers do not reverse for RTL locales.
+ */
interface M {
val ViewportWidth: Float
val ViewportHeight: Float
- // Bounds, oriented in the above viewport, where we will fit-center and center-align
- // an attribution that is the sole foreground element
- val AttrFullCanvas: RectF
- // Bounds, oriented in the above viewport, where we will fit-center and left-align
- // an attribution that is sharing space with the percent text of the drawable
- val AttrRightCanvas: RectF
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the full canvas for a single
+ * foreground element. The element will be fit-center and center-aligned on this canvas
+ *
+ * 18x8 point size
+ */
+ val AttrFullCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in LTR, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsets: RectF
+
+ /**
+ * Insets, oriented in the above viewport in RTL, that define the partial canvas for a
+ * foreground element that shares space with the percent text. The element will be
+ * fit-center and left-aligned on this canvas.
+ *
+ * 6x6 point size
+ */
+ val AttrRightCanvasInsetsRtl: RectF
}
companion object {
@@ -220,20 +294,9 @@
override val ViewportWidth: Float = 24f
override val ViewportHeight: Float = 14f
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and center-align
- * an attribution that is the sole foreground element
- *
- * 18x8 point size
- */
- override val AttrFullCanvas: RectF = RectF(4f, 3f, 22f, 11f)
- /**
- * Bounds, oriented in the above viewport, where we will fit-center and left-align
- * an attribution that is sharing space with the percent text of the drawable
- *
- * 6x6 point size
- */
- override val AttrRightCanvas: RectF = RectF(16f, 4f, 22f, 10f)
+ override val AttrFullCanvasInsets = RectF(4f, 3f, 2f, 3f)
+ override val AttrRightCanvasInsets = RectF(16f, 4f, 2f, 4f)
+ override val AttrRightCanvasInsetsRtl = RectF(14f, 4f, 4f, 4f)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
index 123d6ba..aa0e373 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryPercentTextOnlyDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -71,6 +72,7 @@
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -81,11 +83,12 @@
val totalAvailableWidth = CanvasWidth * hScale
val textWidth = textPaint.measureText(percentText)
val offsetX = (totalAvailableWidth - textWidth) / 2
+ val startOffset = if (rtl) ViewportInsetRight else ViewportInsetLeft
// Draw the text centered in the available area
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ (startOffset * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
index 0c418b9..3b4c779 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatterySpaceSharingPercentTextDrawable.kt
@@ -23,6 +23,7 @@
import android.graphics.Rect
import android.graphics.Typeface
import android.graphics.drawable.Drawable
+import android.view.View
import com.android.systemui.battery.unified.BatteryLayersDrawable.Companion.Metrics
/**
@@ -94,6 +95,7 @@
}
override fun draw(canvas: Canvas) {
+ val rtl = layoutDirection == View.LAYOUT_DIRECTION_RTL
val totalAvailableHeight = CanvasHeight * vScale
// Distribute the vertical whitespace around the text. This is a simplified version of
@@ -107,7 +109,7 @@
canvas.drawText(
percentText,
- (ViewportInsetLeft * hScale) + offsetX,
+ ((if (rtl) ViewportInsetLeftRtl else ViewportInsetLeft) * hScale) + offsetX,
(ViewportInsetTop * vScale) + offsetY,
textPaint
)
@@ -128,6 +130,7 @@
companion object {
private const val ViewportInsetLeft = 4f
+ private const val ViewportInsetLeftRtl = 2f
private const val ViewportInsetTop = 2f
private const val CanvasWidth = 12f
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
index 6af0fa0..66aeda6 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java
@@ -42,7 +42,7 @@
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaDataUtils;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -69,7 +69,7 @@
private final Context mContext;
private final UiEventLogger mUiEventLogger;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final LocalBluetoothManager mLocalBluetoothManager;
private final BroadcastSender mBroadcastSender;
private final SystemUIDialog.Factory mSystemUIDialogFactory;
@@ -157,7 +157,7 @@
@AssistedInject
BroadcastDialogDelegate(
Context context,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
@Nullable LocalBluetoothManager localBluetoothManager,
UiEventLogger uiEventLogger,
@Background Executor bgExecutor,
@@ -166,7 +166,7 @@
@Assisted(CURRENT_BROADCAST_APP) String currentBroadcastApp,
@Assisted(OUTPUT_PKG_NAME) String outputPkgName) {
mContext = context;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mLocalBluetoothManager = localBluetoothManager;
mSystemUIDialogFactory = systemUIDialogFactory;
mCurrentBroadcastApp = currentBroadcastApp;
@@ -218,7 +218,7 @@
R.string.bt_le_audio_broadcast_dialog_switch_app, switchBroadcastApp), null);
mSwitchBroadcast.setOnClickListener((view) -> startSwitchBroadcast());
changeOutput.setOnClickListener((view) -> {
- mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+ mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null);
dialog.dismiss();
});
cancelBtn.setOnClickListener((view) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 1c8b84d..b42eda1 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -73,6 +73,14 @@
initialValue = isInputEnabled.value && !isTextFieldFocused.value,
)
+ /** The ID of the currently-selected user. */
+ val selectedUserId: StateFlow<Int> =
+ selectedUserInteractor.selectedUser.stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = selectedUserInteractor.getSelectedUserId(),
+ )
+
override fun onHidden() {
super.onHidden()
isTextFieldFocused.value = false
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index d5cb4d5..c3c7411 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -24,18 +24,15 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
@@ -66,19 +63,21 @@
.onEach { nextScene -> communalInteractor.onSceneChanged(nextScene) }
.launchIn(applicationScope)
+ // TODO(b/322787129): re-enable once custom animations are in place
// Handle automatically switching to communal when docked.
- dockManager
- .retrieveIsDocked()
- // Allow some time after docking to ensure the dream doesn't start. If the dream
- // starts, then we don't want to automatically transition to glanceable hub.
- .debounce(DOCK_DEBOUNCE_DELAY)
- .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
- .onEach { (docked, lastStartedState) ->
- if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
- communalInteractor.onSceneChanged(CommunalScenes.Communal)
- }
- }
- .launchIn(bgScope)
+ // dockManager
+ // .retrieveIsDocked()
+ // // Allow some time after docking to ensure the dream doesn't start. If the
+ // dream
+ // // starts, then we don't want to automatically transition to glanceable hub.
+ // .debounce(DOCK_DEBOUNCE_DELAY)
+ // .sample(keyguardTransitionInteractor.startedKeyguardState, ::Pair)
+ // .onEach { (docked, lastStartedState) ->
+ // if (docked && lastStartedState == KeyguardState.LOCKSCREEN) {
+ // communalInteractor.onSceneChanged(CommunalScenes.Communal)
+ // }
+ // }
+ // .launchIn(bgScope)
}
private suspend fun determineSceneAfterTransition(
@@ -89,7 +88,7 @@
val docked = dockManager.isDocked
return when {
- docked && to == KeyguardState.LOCKSCREEN && from != KeyguardState.GLANCEABLE_HUB -> {
+ docked && to == KeyguardState.LOCKSCREEN && from == KeyguardState.DREAMING -> {
CommunalScenes.Communal
}
to == KeyguardState.GONE -> CommunalScenes.Blank
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 79455eb..289dbd9 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -24,9 +24,12 @@
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.shared.model.BiometricMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -60,17 +63,23 @@
private val context: Context,
activityStarter: ActivityStarter,
powerInteractor: PowerInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
private val keyguardOccludedByApp: Flow<Boolean> =
- combine(
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isKeyguardShowing,
- primaryBouncerInteractor.isShowing,
- alternateBouncerInteractor.isVisible,
- ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
- occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
- }
- .distinctUntilChanged()
+ if (KeyguardWmStateRefactor.isEnabled) {
+ keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ } else {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isKeyguardShowing,
+ primaryBouncerInteractor.isShowing,
+ alternateBouncerInteractor.isVisible,
+ ) { occluded, showing, primaryBouncerShowing, alternateBouncerVisible ->
+ occluded && showing && !primaryBouncerShowing && !alternateBouncerVisible
+ }
+ .distinctUntilChanged()
+ }
+
private val fingerprintUnlockSuccessEvents: Flow<Unit> =
fingerprintAuthRepository.authenticationStatus
.ifKeyguardOccludedByApp()
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
index c0a873a..989b0de 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/view/MirroringConfirmationDialog.kt
@@ -18,6 +18,7 @@
import android.content.Context
import android.os.Bundle
import android.view.View
+import android.view.WindowInsets
import android.widget.TextView
import androidx.core.view.updatePadding
import com.android.systemui.res.R
@@ -44,7 +45,10 @@
private lateinit var mirrorButton: TextView
private lateinit var dismissButton: TextView
private lateinit var dualDisplayWarning: TextView
+ private lateinit var bottomSheet: View
private var enabledPressed = false
+ private val defaultDialogBottomInset =
+ context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -63,6 +67,8 @@
visibility = if (showConcurrentDisplayInfo) View.VISIBLE else View.GONE
}
+ bottomSheet = requireViewById(R.id.cd_bottom_sheet)
+
setOnDismissListener {
if (!enabledPressed) {
onCancelMirroring.onClick(null)
@@ -71,15 +77,17 @@
setupInsets()
}
- private fun setupInsets() {
+ private fun setupInsets(navbarInsets: Int = navbarBottomInsetsProvider()) {
// This avoids overlap between dialog content and navigation bars.
- requireViewById<View>(R.id.cd_bottom_sheet).apply {
- val navbarInsets = navbarBottomInsetsProvider()
- val defaultDialogBottomInset =
- context.resources.getDimensionPixelSize(R.dimen.dialog_bottom_padding)
- // we only care about the bottom inset as in all other configuration where navigations
- // are in other display sides there is no overlap with the dialog.
- updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ // we only care about the bottom inset as in all other configuration where navigations
+ // are in other display sides there is no overlap with the dialog.
+ bottomSheet.updatePadding(bottom = max(navbarInsets, defaultDialogBottomInset))
+ }
+
+ override fun onInsetsChanged(changedTypes: Int, insets: WindowInsets) {
+ val navbarType = WindowInsets.Type.navigationBars()
+ if (changedTypes and navbarType != 0) {
+ setupInsets(insets.getInsets(navbarType).bottom)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
index 190062c..fbf0538 100644
--- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt
@@ -32,9 +32,12 @@
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.launch
@@ -57,6 +60,7 @@
private var dialog: Dialog? = null
/** Starts listening for pending displays. */
+ @OptIn(FlowPreview::class)
override fun start() {
val pendingDisplayFlow = connectedDisplayInteractor.pendingDisplay
val concurrentDisplaysInProgessFlow =
@@ -66,6 +70,13 @@
flow { emit(false) }
}
pendingDisplayFlow
+ // Let's debounce for 2 reasons:
+ // - prevent fast dialog flashes in case pending displays are available for just a few
+ // millis
+ // - Prevent jumps related to inset changes: when in 3 buttons navigation, device
+ // unlock triggers a change in insets that might result in a jump of the dialog (if a
+ // display was connected while on the lockscreen).
+ .debounce(200.milliseconds)
.combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress
->
if (pendingDisplay == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index e74814a..376d312 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.homecontrols
import android.content.Intent
+import android.os.PowerManager
import android.service.controls.ControlsProviderService
import android.service.dreams.DreamService
import android.window.TaskFragmentInfo
@@ -27,6 +28,8 @@
import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor.Companion.MAX_UPDATE_CORRELATION_DELAY
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.DreamLog
+import com.android.systemui.util.wakelock.WakeLock
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,14 +45,23 @@
private val controlsSettingsRepository: ControlsSettingsRepository,
private val taskFragmentFactory: TaskFragmentComponent.Factory,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
+ private val wakeLockBuilder: WakeLock.Builder,
private val dreamActivityProvider: DreamActivityProvider,
@Background private val bgDispatcher: CoroutineDispatcher,
@DreamLog logBuffer: LogBuffer
) : DreamService() {
+
private val serviceJob = SupervisorJob()
private val serviceScope = CoroutineScope(bgDispatcher + serviceJob)
- private val logger = DreamLogger(logBuffer, "HomeControlsDreamService")
+ private val logger = DreamLogger(logBuffer, TAG)
private lateinit var taskFragmentComponent: TaskFragmentComponent
+ private val wakeLock: WakeLock by lazy {
+ wakeLockBuilder
+ .setMaxTimeout(NO_TIMEOUT)
+ .setTag(TAG)
+ .setLevelsAndFlags(PowerManager.SCREEN_BRIGHT_WAKE_LOCK)
+ .build()
+ }
override fun onAttachedToWindow() {
super.onAttachedToWindow()
@@ -72,6 +84,8 @@
hide = { finish() }
)
.apply { createTaskFragment() }
+
+ wakeLock.acquire(TAG)
}
private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
@@ -100,6 +114,7 @@
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
+ wakeLock.release(TAG)
taskFragmentComponent.destroy()
serviceScope.launch {
delay(CANCELLATION_DELAY_AFTER_DETACHED)
@@ -115,5 +130,6 @@
* complete.
*/
val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+ const val TAG = "HomeControlsDreamService"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
index 931a869..ed82278 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt
@@ -163,6 +163,6 @@
}
companion object {
- const val KEY_UP_TIMEOUT = 100L
+ const val KEY_UP_TIMEOUT = 60L
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 2e233d8..3134e35 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -289,6 +289,8 @@
};
}
+ private final WindowManagerOcclusionManager mWmOcclusionManager;
+
@Inject
public KeyguardService(
KeyguardViewMediator keyguardViewMediator,
@@ -302,7 +304,8 @@
KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
@Application CoroutineScope scope,
FeatureFlags featureFlags,
- PowerInteractor powerInteractor) {
+ PowerInteractor powerInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
@@ -323,6 +326,8 @@
keyguardSurfaceBehindAnimator,
scope);
}
+
+ mWmOcclusionManager = windowManagerOcclusionManager;
}
@Override
@@ -414,7 +419,11 @@
Trace.beginSection("KeyguardService.mBinder#setOccluded");
checkPermission();
- mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewMediator.setOccluded(isOccluded, animate);
+ } else {
+ mWmOcclusionManager.onKeyguardServiceSetOccluded(isOccluded);
+ }
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6d917bb..a37397d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1365,7 +1365,9 @@
private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+ private WindowManagerOcclusionManager mWmOcclusionManager;
/**
+
* Injected constructor. See {@link KeyguardModule}.
*/
public KeyguardViewMediator(
@@ -1411,7 +1413,8 @@
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1486,6 +1489,8 @@
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
+
+ mWmOcclusionManager = wmOcclusionManager;
}
public void userActivity() {
@@ -2103,15 +2108,27 @@
}
public IRemoteAnimationRunner getOccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
+ }
}
+ /**
+ * TODO(b/326464548): Move this to WindowManagerOcclusionManager
+ */
public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
}
public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
- return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ return validatingRemoteAnimationRunner(
+ mWmOcclusionManager.getUnoccludeAnimationRunner());
+ } else {
+ return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
+ }
}
public boolean isHiding() {
@@ -2145,8 +2162,10 @@
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
- mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
- && mDeviceInteractive);
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardViewControllerLazy.get().setOccluded(isOccluded, animate
+ && mDeviceInteractive);
+ }
adjustStatusBarLocked();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 8ebcece..00f5002 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -140,8 +140,14 @@
nonApps: Array<RemoteAnimationTarget>,
finishedCallback: IRemoteAnimationFinishedCallback
) {
- goingAwayRemoteAnimationFinishedCallback = finishedCallback
- keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ if (apps.isNotEmpty()) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ } else {
+ // Nothing to do here if we have no apps, end the animation, which will cancel it and WM
+ // will make *something* visible.
+ finishedCallback.onAnimationFinished()
+ }
}
fun onKeyguardGoingAwayRemoteAnimationCancelled() {
@@ -174,13 +180,19 @@
* if so, true should be the right choice.
*/
private fun setWmLockscreenState(
- lockscreenShowing: Boolean = this.isLockscreenShowing ?: true.also {
- Log.d(TAG, "Using isLockscreenShowing=true default in setWmLockscreenState, " +
- "because setAodVisible was called before the first setLockscreenShown " +
- "call during boot. This is not typical, but is theoretically possible. " +
- "If you're investigating the lockscreen showing unexpectedly, start here.")
- },
- aodVisible: Boolean = this.isAodVisible
+ lockscreenShowing: Boolean =
+ this.isLockscreenShowing
+ ?: true.also {
+ Log.d(
+ TAG,
+ "Using isLockscreenShowing=true default in setWmLockscreenState, " +
+ "because setAodVisible was called before the first " +
+ "setLockscreenShown call during boot. This is not typical, but is " +
+ "theoretically possible. If you're investigating the lockscreen " +
+ "showing unexpectedly, start here."
+ )
+ },
+ aodVisible: Boolean = this.isAodVisible
) {
Log.d(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
new file mode 100644
index 0000000..aab90c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Matrix
+import android.os.RemoteException
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationTarget
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.TransitionAnimator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.res.R
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private val UNOCCLUDE_ANIMATION_DURATION = 250
+private val UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT = 0.1f
+
+/**
+ * Keeps track of Window Manager's occlusion state and RemoteAnimations related to changes in
+ * occlusion state. Occlusion is when a [FLAG_SHOW_WHEN_LOCKED] activity is displaying over the
+ * lockscreen - we're still locked, but the user can interact with the activity.
+ *
+ * Typical "occlusion" use cases include launching the camera over the lockscreen, tapping a quick
+ * affordance to bring up Google Pay/Wallet/whatever it's called by the time you're reading this,
+ * and Maps Navigation.
+ *
+ * Window Manager considers the keyguard to be 'occluded' whenever a [FLAG_SHOW_WHEN_LOCKED]
+ * activity is on top of the task stack, even if the device is unlocked and the keyguard is not
+ * visible. System UI considers the keyguard to be [KeyguardState.OCCLUDED] only when we're on the
+ * keyguard and an activity is displaying over it.
+ *
+ * For all System UI use cases, you should use [KeyguardTransitionInteractor] to determine if we're
+ * in the [KeyguardState.OCCLUDED] state and react accordingly. If you are sure that you need to
+ * check whether Window Manager considers OCCLUDED=true even though the lockscreen is not showing,
+ * use [KeyguardShowWhenLockedActivityInteractor.isShowWhenLockedActivityOnTop] in combination with
+ * [KeyguardTransitionInteractor] state.
+ *
+ * This is a very sensitive piece of state that has caused many headaches in the past. Please be
+ * careful.
+ */
+@SysUISingleton
+class WindowManagerOcclusionManager
+@Inject
+constructor(
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val activityTransitionAnimator: ActivityTransitionAnimator,
+ val keyguardViewController: dagger.Lazy<KeyguardViewController>,
+ val powerInteractor: PowerInteractor,
+ val context: Context,
+ val interactionJankMonitor: InteractionJankMonitor,
+ @Main executor: Executor,
+ val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+ val occlusionInteractor: KeyguardOcclusionInteractor,
+) {
+ val powerButtonY =
+ context.resources.getDimensionPixelSize(
+ R.dimen.physical_power_button_center_screen_location_y
+ )
+ val windowCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
+
+ var occludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * launched and Window Manager wants us to animate it in. This is used as a signal that we are
+ * now occluded, and should update our state accordingly.
+ */
+ val occludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ occludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ occludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = true,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ activityTransitionAnimator
+ .createRunner(occludeAnimationController)
+ .onAnimationStart(
+ transit,
+ apps,
+ wallpapers,
+ nonApps,
+ occludeAnimationFinishedCallback,
+ )
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "occludeAnimationRunner#onAnimationCancelled")
+ }
+ }
+
+ var unoccludeAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Animation runner provided to WindowManager, which will be used if an occluding activity is
+ * finished and Window Manager wants us to animate it out. This is used as a signal that we are
+ * no longer occluded, and should update our state accordingly.
+ *
+ * TODO(b/326464548): Restore dream specific animation.
+ */
+ val unoccludeAnimationRunner: IRemoteAnimationRunner =
+ object : IRemoteAnimationRunner.Stub() {
+ var unoccludeAnimator: ValueAnimator? = null
+ val unoccludeMatrix = Matrix()
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun onAnimationStart(
+ transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback?
+ ) {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationStart")
+ // Wrap the callback so that it's guaranteed to be nulled out once called.
+ unoccludeAnimationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback.Stub() {
+ override fun onAnimationFinished() {
+ finishedCallback?.onAnimationFinished()
+ unoccludeAnimationFinishedCallback = null
+ }
+ }
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = false,
+ taskInfo = apps.firstOrNull()?.taskInfo,
+ )
+ interactionJankMonitor.begin(
+ createInteractionJankMonitorConf(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION,
+ "UNOCCLUDE"
+ )
+ )
+ if (apps.isEmpty()) {
+ Log.d(
+ TAG,
+ "No apps provided to unocclude runner; " +
+ "skipping animation and unoccluding."
+ )
+ unoccludeAnimationFinishedCallback?.onAnimationFinished()
+ return
+ }
+ val target = apps[0]
+ val localView: View = keyguardViewController.get().getViewRootImpl().getView()
+ val applier = SyncRtSurfaceTransactionApplier(localView)
+ // TODO(
+ executor.execute {
+ unoccludeAnimator?.cancel()
+ unoccludeAnimator =
+ ValueAnimator.ofFloat(1f, 0f).apply {
+ duration = UNOCCLUDE_ANIMATION_DURATION.toLong()
+ interpolator = Interpolators.TOUCH_RESPONSE
+ addUpdateListener { animation: ValueAnimator ->
+ val animatedValue = animation.animatedValue as Float
+ val surfaceHeight: Float =
+ target.screenSpaceBounds.height().toFloat()
+
+ unoccludeMatrix.setTranslate(
+ 0f,
+ (1f - animatedValue) *
+ surfaceHeight *
+ UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT
+ )
+
+ SurfaceParams.Builder(target.leash)
+ .withAlpha(animatedValue)
+ .withMatrix(unoccludeMatrix)
+ .withCornerRadius(windowCornerRadius)
+ .build()
+ .also { applier.scheduleApply(it) }
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ try {
+ unoccludeAnimationFinishedCallback
+ ?.onAnimationFinished()
+ unoccludeAnimator = null
+ interactionJankMonitor.end(
+ InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION
+ )
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ )
+ start()
+ }
+ }
+ }
+
+ override fun onAnimationCancelled() {
+ Log.d(TAG, "unoccludeAnimationRunner#onAnimationCancelled")
+ context.mainExecutor.execute { unoccludeAnimator?.cancel() }
+ Log.d(TAG, "Unocclude animation cancelled.")
+ interactionJankMonitor.cancel(InteractionJankMonitor.CUJ_LOCKSCREEN_OCCLUSION)
+ }
+ }
+
+ /**
+ * Called when Window Manager tells the KeyguardService directly that we're occluded or not
+ * occluded, without starting an occlude/unocclude remote animation. This happens if occlusion
+ * state changes without an animation (such as if a SHOW_WHEN_LOCKED activity is launched while
+ * we're unlocked), or if an animation has been cancelled/interrupted and Window Manager wants
+ * to make sure that we're in the correct state.
+ */
+ fun onKeyguardServiceSetOccluded(occluded: Boolean) {
+ Log.d(TAG, "#onKeyguardServiceSetOccluded($occluded)")
+ keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(occluded)
+ }
+
+ @VisibleForTesting
+ val occludeAnimationController: ActivityTransitionAnimator.Controller =
+ object : ActivityTransitionAnimator.Controller {
+
+ override var transitionContainer: ViewGroup
+ get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
+ set(_) {
+ // Should never be set.
+ }
+
+ /** TODO(b/326470033): Extract this logic into ViewModels. */
+ override fun createAnimatorState(): TransitionAnimator.State {
+ val fullWidth = transitionContainer.width
+ val fullHeight = transitionContainer.height
+
+ if (
+ keyguardOcclusionInteractor.showWhenLockedActivityLaunchedFromPowerGesture.value
+ ) {
+ val initialHeight = fullHeight / 3f
+ val initialWidth = fullWidth / 3f
+
+ // Start the animation near the power button, at one-third size, since the
+ // camera was launched from the power button.
+ return TransitionAnimator.State(
+ top = (powerButtonY - initialHeight / 2f).toInt(),
+ bottom = (powerButtonY + initialHeight / 2f).toInt(),
+ left = (fullWidth - initialWidth).toInt(),
+ right = fullWidth,
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ } else {
+ val initialHeight = fullHeight / 2f
+ val initialWidth = fullWidth / 2f
+
+ // Start the animation in the center of the screen, scaled down to half
+ // size.
+ return TransitionAnimator.State(
+ top = (fullHeight - initialHeight).toInt() / 2,
+ bottom = (initialHeight + (fullHeight - initialHeight) / 2).toInt(),
+ left = (fullWidth - initialWidth).toInt() / 2,
+ right = (initialWidth + (fullWidth - initialWidth) / 2).toInt(),
+ topCornerRadius = windowCornerRadius,
+ bottomCornerRadius = windowCornerRadius,
+ )
+ }
+ }
+ }
+
+ private fun createInteractionJankMonitorConf(
+ cuj: Int,
+ tag: String?
+ ): InteractionJankMonitor.Configuration.Builder {
+ val builder =
+ InteractionJankMonitor.Configuration.Builder.withView(
+ cuj,
+ keyguardViewController.get().getViewRootImpl().view
+ )
+ return if (tag != null) builder.setTag(tag) else builder
+ }
+
+ companion object {
+ val TAG = "WindowManagerOcclusion"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 968c3e3..5306645 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -50,6 +50,7 @@
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
+import com.android.systemui.keyguard.WindowManagerOcclusionManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -163,7 +164,8 @@
SystemPropertiesHelper systemPropertiesHelper,
Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
SelectedUserInteractor selectedUserInteractor,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -209,7 +211,8 @@
systemPropertiesHelper,
wmLockscreenVisibilityManager,
selectedUserInteractor,
- keyguardInteractor);
+ keyguardInteractor,
+ windowManagerOcclusionManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
new file mode 100644
index 0000000..e3654b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepository.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/**
+ * Information about the SHOW_WHEN_LOCKED activity that is either newly on top of the task stack, or
+ * newly not on top of the task stack.
+ */
+data class ShowWhenLockedActivityInfo(
+ /** Whether the activity is on top. If not, we're unoccluding and will be animating it out. */
+ val isOnTop: Boolean,
+
+ /**
+ * Information about the activity, which we use for transition internals and also to customize
+ * animations.
+ */
+ val taskInfo: RunningTaskInfo? = null
+) {
+ fun isDream(): Boolean {
+ return taskInfo?.topActivityType == WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+}
+
+/**
+ * Maintains state about "occluding" activities - activities with FLAG_SHOW_WHEN_LOCKED, which are
+ * capable of displaying over the lockscreen while the device is still locked (such as Google Maps
+ * navigation).
+ *
+ * Window Manager considers the device to be in the "occluded" state whenever such an activity is on
+ * top of the task stack, including while we're unlocked, while keyguard code considers us to be
+ * occluded only when we're locked, with an occluding activity currently displaying over the
+ * lockscreen.
+ *
+ * This dual definition is confusing, so this repository collects all of the signals WM gives us,
+ * and consolidates them into [showWhenLockedActivityInfo.isOnTop], which is the actual question WM
+ * is answering when they say whether we're 'occluded'. Keyguard then uses this signal to
+ * conditionally transition to [KeyguardState.OCCLUDED] where appropriate.
+ */
+@SysUISingleton
+class KeyguardOcclusionRepository @Inject constructor() {
+ val showWhenLockedActivityInfo = MutableStateFlow(ShowWhenLockedActivityInfo(isOnTop = false))
+
+ /**
+ * Sets whether there's a SHOW_WHEN_LOCKED activity on top of the task stack, and optionally,
+ * information about the activity itself.
+ *
+ * If no value is provided for [taskInfo], we'll default to the current [taskInfo].
+ *
+ * The [taskInfo] is always present when this method is called from the occlude/unocclude
+ * animation runners. We use the default when calling from [KeyguardService.isOccluded], since
+ * we only receive a true/false value there. isOccluded is mostly redundant - it's almost always
+ * called with true after an occlusion animation has started, and with false after an unocclude
+ * animation has started. In those cases, we don't want to clear out the taskInfo just because
+ * it wasn't available at that call site.
+ */
+ fun setShowWhenLockedActivityInfo(
+ onTop: Boolean,
+ taskInfo: RunningTaskInfo? = showWhenLockedActivityInfo.value.taskInfo
+ ) {
+ showWhenLockedActivityInfo.value =
+ ShowWhenLockedActivityInfo(
+ isOnTop = onTop,
+ taskInfo = taskInfo,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index ad0c3fb..1298fa5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -88,6 +88,7 @@
val isKeyguardShowing: Flow<Boolean>
/** Is an activity showing over the keyguard? */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean>
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 9b3f13d..d9479de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -75,7 +75,6 @@
powerInteractor: PowerInteractor,
private val scrimLogger: ScrimLogger,
) : LightRevealScrimRepository {
-
companion object {
val TAG = LightRevealScrimRepository::class.simpleName!!
}
@@ -156,7 +155,11 @@
override fun startRevealAmountAnimator(reveal: Boolean) {
if (reveal == willBeOrIsRevealed) return
willBeOrIsRevealed = reveal
- if (reveal) revealAmountAnimator.start() else revealAmountAnimator.reverse()
+ if (reveal && !revealAmountAnimator.isRunning) {
+ revealAmountAnimator.start()
+ } else {
+ revealAmountAnimator.reverse()
+ }
scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
index ee892a8..ca86289 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractor.kt
@@ -19,7 +19,7 @@
import android.content.Context
import androidx.annotation.DimenRes
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -47,13 +47,15 @@
private val context: Context,
private val burnInHelperWrapper: BurnInHelperWrapper,
@Application private val scope: CoroutineScope,
- private val configurationRepository: ConfigurationRepository,
+ private val configurationInteractor: ConfigurationInteractor,
private val keyguardInteractor: KeyguardInteractor,
) {
val deviceEntryIconXOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_x, isXAxis = true)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val deviceEntryIconYOffset: StateFlow<Int> =
burnInOffsetDefinedInPixels(R.dimen.udfps_burn_in_offset_y, isXAxis = false)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val udfpsProgress: StateFlow<Float> =
keyguardInteractor.dozeTimeTick
.mapLatest { burnInHelperWrapper.burnInProgressOffset() }
@@ -74,7 +76,6 @@
BurnInModel(translationX, translationY, burnInHelperWrapper.burnInScale())
}
.distinctUntilChanged()
- .stateIn(scope, SharingStarted.Lazily, BurnInModel())
}
/**
@@ -85,23 +86,14 @@
private fun burnInOffset(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
- ): StateFlow<Int> {
- return configurationRepository.onAnyConfigurationChange
- .flatMapLatest {
- val maxBurnInOffsetPixels =
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- keyguardInteractor.dozeTimeTick.mapLatest {
- calculateOffset(maxBurnInOffsetPixels, isXAxis)
- }
+ ): Flow<Int> {
+ return configurationInteractor.onAnyConfigurationChange.flatMapLatest {
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis)
}
- .stateIn(
- scope,
- SharingStarted.Lazily,
- calculateOffset(
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
- isXAxis,
- )
- )
+ }
}
/**
@@ -112,24 +104,14 @@
private fun burnInOffsetDefinedInPixels(
@DimenRes maxBurnInOffsetResourceId: Int,
isXAxis: Boolean,
- ): StateFlow<Int> {
- return configurationRepository.scaleForResolution
- .flatMapLatest { scale ->
- val maxBurnInOffsetPixels =
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
- keyguardInteractor.dozeTimeTick.mapLatest {
- calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
- }
+ ): Flow<Int> {
+ return configurationInteractor.scaleForResolution.flatMapLatest { scale ->
+ val maxBurnInOffsetPixels =
+ context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId)
+ keyguardInteractor.dozeTimeTick.mapLatest {
+ calculateOffset(maxBurnInOffsetPixels, isXAxis, scale)
}
- .stateIn(
- scope,
- SharingStarted.WhileSubscribed(),
- calculateOffset(
- context.resources.getDimensionPixelSize(maxBurnInOffsetResourceId),
- isXAxis,
- configurationRepository.getResolutionScale(),
- )
- )
+ }
}
private fun calculateOffset(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 6aed944..88ddfd4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -21,6 +21,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -46,13 +47,16 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.ALTERNATE_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -112,6 +116,11 @@
}
private fun listenForAlternateBouncerToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAlternateBouncer.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sampleUtil(finishedKeyguardState, ::Pair)
@@ -149,6 +158,10 @@
}
}
+ fun dismissAlternateBouncer() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
companion object {
const val TAG = "FromAlternateBouncerTransitionInteractor"
val TRANSITION_DURATION_MS = 300.milliseconds
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index dbd5e26..9040e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -27,14 +27,17 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
+import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -47,29 +50,118 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.AOD,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
- listenForAodToLockscreen()
+ listenForAodToAwake()
+ listenForAodToOccluded()
listenForAodToPrimaryBouncer()
listenForAodToGone()
- listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
/**
+ * Listen for the signal that we're waking up and figure what state we need to transition to.
+ */
+ private fun listenForAodToAwake() {
+ val transitionToLockscreen: suspend (TransitionStep) -> UUID? =
+ { startedStep: TransitionStep ->
+ val modeOnCanceled =
+ if (startedStep.from == KeyguardState.LOCKSCREEN) {
+ TransitionModeOnCanceled.REVERSE
+ } else if (startedStep.from == KeyguardState.GONE) {
+ TransitionModeOnCanceled.RESET
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ startTransitionTo(
+ toState = KeyguardState.LOCKSCREEN,
+ modeOnCanceled = modeOnCanceled,
+ )
+ }
+
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // The refactor uses PowerInteractor's wakefulness, which is the earliest wake signal
+ // available. We have all of the information we need at this time to make a decision
+ // about where to transition.
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ // React only to wake events.
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // Make sure we've at least STARTED a transition to AOD.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.AOD }
+ .collect { (_, startedStep, biometricUnlockState, primaryBouncerShowing) ->
+ // Check with the superclass to see if an occlusion transition is needed.
+ // Also, don't react to wake and unlock events, as we'll be receiving a call
+ // to #dismissAod() shortly when the authentication completes.
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(startedStep)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor
+ .dozeTransitionTo(DozeStateModel.FINISH)
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.biometricUnlockState,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ .collect {
+ (
+ _,
+ isKeyguardShowing,
+ lastStartedStep,
+ occluded,
+ biometricUnlockState,
+ primaryBouncerShowing) ->
+ if (
+ lastStartedStep.to == KeyguardState.AOD &&
+ !occluded &&
+ !isWakeAndUnlock(biometricUnlockState) &&
+ isKeyguardShowing &&
+ !primaryBouncerShowing
+ ) {
+ transitionToLockscreen(lastStartedStep)
+ }
+ }
+ }
+ }
+ }
+
+ /**
* There are cases where the transition to AOD begins but never completes, such as tapping power
* during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
* run AOD->OCCLUDED.
*/
private fun listenForAodToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled by calls to maybeStartTransitionToOccludedOrInsecureCamera on waking.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(startedKeyguardTransitionStep, ::Pair)
@@ -84,49 +176,6 @@
}
}
- private fun listenForAodToLockscreen() {
- scope.launch {
- keyguardInteractor
- .dozeTransitionTo(DozeStateModel.FINISH)
- .sample(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.biometricUnlockState,
- keyguardInteractor.primaryBouncerShowing,
- )
- .collect {
- (
- _,
- isKeyguardShowing,
- lastStartedStep,
- occluded,
- biometricUnlockState,
- primaryBouncerShowing) ->
- if (
- lastStartedStep.to == KeyguardState.AOD &&
- !occluded &&
- !isWakeAndUnlock(biometricUnlockState) &&
- isKeyguardShowing &&
- !primaryBouncerShowing
- ) {
- val modeOnCanceled =
- if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
- TransitionModeOnCanceled.REVERSE
- } else if (lastStartedStep.from == KeyguardState.GONE) {
- TransitionModeOnCanceled.RESET
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = KeyguardState.LOCKSCREEN,
- modeOnCanceled = modeOnCanceled,
- )
- }
- }
- }
- }
-
/**
* If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
* PRIMARY_BOUNCER.
@@ -145,6 +194,7 @@
private fun listenForAodToGone() {
if (KeyguardWmStateRefactor.isEnabled) {
+ // Handled via #dismissAod.
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 8591fe7..57b2a63 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -22,6 +22,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -34,6 +35,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,18 +48,22 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DOZING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDozingToAny()
+ listenForWakeFromDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
}
@@ -70,6 +76,10 @@
}
private fun listenForDozingToAny() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
powerInteractor.isAwake
.debounce(50L)
@@ -112,6 +122,58 @@
}
}
+ /** Figure out what state to transition to when we awake from DOZING. */
+ private fun listenForWakeFromDozing() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
+ scope.launch {
+ powerInteractor.detailedWakefulness
+ .filter { it.isAwake() }
+ .sample(
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ keyguardInteractor.biometricUnlockState,
+ canDismissLockScreen,
+ keyguardInteractor.primaryBouncerShowing,
+ )
+ // If we haven't at least STARTED a transition to DOZING, ignore.
+ .filter { (_, startedStep, _, _) -> startedStep.to == KeyguardState.DOZING }
+ .collect {
+ (
+ _,
+ _,
+ isIdleOnCommunal,
+ biometricUnlockState,
+ canDismissLockscreen,
+ primaryBouncerShowing) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ // Handled by dismissFromDozing().
+ !isWakeAndUnlock(biometricUnlockState)
+ ) {
+ startTransitionTo(
+ if (canDismissLockscreen) {
+ KeyguardState.GONE
+ } else if (primaryBouncerShowing) {
+ KeyguardState.PRIMARY_BOUNCER
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ )
+ }
+ }
+ }
+ }
+
+ /** Dismisses keyguard from the DOZING state. */
+ fun dismissFromDozing() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
return ValueAnimator().apply {
interpolator = Interpolators.LINEAR
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index a6cdaa8..6433d0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -25,6 +25,7 @@
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -46,12 +47,16 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING_LOCKSCREEN_HOSTED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index acfa107..3137138 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -23,10 +23,14 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.Utils
+import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -34,6 +38,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -47,17 +52,23 @@
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.DREAMING,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForDreamingToOccluded()
- listenForDreamingToGone()
+ listenForDreamingToGoneWhenDismissable()
+ listenForDreamingToGoneFromBiometricUnlock()
+ listenForDreamingToLockscreen()
listenForDreamingToAodOrDozing()
listenForTransitionToCamera(scope, keyguardInteractor)
listenForDreamingToGlanceableHub()
@@ -76,6 +87,7 @@
fun startToLockscreenTransition() {
scope.launch {
+ KeyguardWmStateRefactor.isUnexpectedlyInLegacyMode()
if (
transitionInteractor.startedKeyguardState.replayCache.last() ==
KeyguardState.DREAMING
@@ -86,22 +98,80 @@
}
private fun listenForDreamingToOccluded() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ combine(
+ keyguardInteractor.isDreaming,
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, ::toTriple)
+ .filter { (isDreaming, _, startedStep) ->
+ !isDreaming && startedStep.to == KeyguardState.DREAMING
+ }
+ .collect { maybeStartTransitionToOccludedOrInsecureCamera() }
+ }
+ } else {
+ scope.launch {
+ combine(
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ ::Pair
+ )
+ .sample(startedKeyguardTransitionStep, Utils.Companion::toTriple)
+ .collect { (isOccluded, isDreaming, lastStartedTransition) ->
+ if (
+ isOccluded &&
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING
+ ) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToLockscreen() {
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
scope.launch {
- combine(keyguardInteractor.isKeyguardOccluded, keyguardInteractor.isDreaming, ::Pair)
- .sample(startedKeyguardTransitionStep, ::toTriple)
- .collect { (isOccluded, isDreaming, lastStartedTransition) ->
- if (
- isOccluded &&
- !isDreaming &&
- lastStartedTransition.to == KeyguardState.DREAMING
- ) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(startedKeyguardState)
+ .collect { startedState ->
+ if (startedState == KeyguardState.DREAMING) {
+ startTransitionTo(KeyguardState.LOCKSCREEN)
}
}
}
}
- private fun listenForDreamingToGone() {
+ private fun listenForDreamingToGoneWhenDismissable() {
+ scope.launch {
+ keyguardInteractor.isAbleToDream
+ .sampleCombine(
+ keyguardInteractor.isKeyguardShowing,
+ keyguardInteractor.isKeyguardDismissible,
+ startedKeyguardTransitionStep,
+ )
+ .collect {
+ (isDreaming, isKeyguardShowing, isKeyguardDismissible, lastStartedTransition) ->
+ if (
+ !isDreaming &&
+ lastStartedTransition.to == KeyguardState.DREAMING &&
+ isKeyguardDismissible &&
+ !isKeyguardShowing
+ ) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForDreamingToGoneFromBiometricUnlock() {
scope.launch {
keyguardInteractor.biometricUnlockState
.sample(startedKeyguardTransitionStep, ::Pair)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 786c3c6..51bc3ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -23,6 +23,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -35,6 +36,7 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -49,14 +51,18 @@
private val keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.GLANCEABLE_HUB,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
+
override fun start() {
if (!Flags.communalHub()) {
return
@@ -151,14 +157,27 @@
}
private fun listenForHubToOccluded() {
- scope.launch {
- and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
- .sample(startedKeyguardState, ::Pair)
- .collect { (isOccludedAndNotDreaming, keyguardState) ->
- if (isOccludedAndNotDreaming && keyguardState == fromState) {
- startTransitionTo(KeyguardState.OCCLUDED)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> onTop }
+ .sample(startedKeyguardState)
+ .collect {
+ if (it == KeyguardState.GLANCEABLE_HUB) {
+ maybeStartTransitionToOccludedOrInsecureCamera()
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming))
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccludedAndNotDreaming, keyguardState) ->
+ if (isOccludedAndNotDreaming && keyguardState == fromState) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 7593ac2..d5a9bd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -22,6 +22,8 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
@@ -34,6 +36,8 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -46,14 +50,18 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val biometricSettingsRepository: BiometricSettingsRepository,
) :
TransitionInteractor(
fromState = KeyguardState.GONE,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
@@ -65,23 +73,46 @@
// Primarily for when the user chooses to lock down the device
private fun listenForGoneToLockscreenOrHub() {
- scope.launch {
- keyguardInteractor.isKeyguardShowing
- .sample(
- startedKeyguardState,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
- if (isKeyguardShowing && startedState == KeyguardState.GONE) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ biometricSettingsRepository.isCurrentUserInLockdown
+ .distinctUntilChanged()
+ .filter { inLockdown -> inLockdown }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ if (startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to, ownerReason = "User initiated lockdown")
+ }
}
- }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardShowing
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isKeyguardShowing, startedState, isIdleOnCommunal) ->
+ if (isKeyguardShowing && startedState == KeyguardState.GONE) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
}
}
@@ -122,24 +153,10 @@
private fun listenForGoneToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.GONE && isAsleep) {
- startTransitionTo(
- toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
- }
- }
+ listenForSleepTransition(
+ from = KeyguardState.GONE,
+ modeOnCanceledFromStartedStep = { TransitionModeOnCanceled.RESET },
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index cb1571e..12b27eb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -33,7 +33,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import javax.inject.Inject
@@ -44,6 +43,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -62,21 +62,24 @@
private val keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
private val swipeToDismissInteractor: SwipeToDismissInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.LOCKSCREEN,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForLockscreenToGone()
listenForLockscreenToGoneDragging()
- listenForLockscreenToOccluded()
+ listenForLockscreenToOccludedOrDreaming()
listenForLockscreenToAodOrDozing()
listenForLockscreenToPrimaryBouncer()
listenForLockscreenToDreaming()
@@ -115,6 +118,10 @@
}
private fun listenForLockscreenToDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ return
+ }
+
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
keyguardInteractor.isAbleToDream
@@ -157,7 +164,10 @@
if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
- startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ startTransitionTo(
+ KeyguardState.PRIMARY_BOUNCER,
+ ownerReason = "#listenForLockscreenToPrimaryBouncer"
+ )
}
}
}
@@ -254,7 +264,8 @@
transitionId =
startTransitionTo(
toState = KeyguardState.PRIMARY_BOUNCER,
- animator = null, // transition will be manually controlled
+ animator = null, // transition will be manually controlled,
+ ownerReason = "#listenForLockscreenToPrimaryBouncerDragging"
)
}
}
@@ -309,47 +320,52 @@
}
}
- private fun listenForLockscreenToOccluded() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
- (isOccluded, keyguardState) ->
- if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
- startTransitionTo(KeyguardState.OCCLUDED)
- }
+ private fun listenForLockscreenToOccludedOrDreaming() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo
+ .filter { it.isOnTop }
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (taskInfo, startedState) ->
+ if (startedState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(
+ if (taskInfo.isDream()) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.OCCLUDED
+ }
+ )
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(startedKeyguardState, ::Pair)
+ .collect { (isOccluded, keyguardState) ->
+ if (isOccluded && keyguardState == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.OCCLUDED)
+ }
+ }
}
}
}
private fun listenForLockscreenToAodOrDozing() {
scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.LOCKSCREEN && isAsleep) {
- val toState =
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- val modeOnCanceled =
- if (
- toState == KeyguardState.AOD &&
- lastStartedStep.from == KeyguardState.AOD
- ) {
- TransitionModeOnCanceled.REVERSE
- } else {
- TransitionModeOnCanceled.LAST_VALUE
- }
- startTransitionTo(
- toState = toState,
- modeOnCanceled = modeOnCanceled,
- )
+ listenForSleepTransition(
+ from = KeyguardState.LOCKSCREEN,
+ modeOnCanceledFromStartedStep = { startedStep ->
+ if (
+ transitionInteractor.asleepKeyguardState.value == KeyguardState.AOD &&
+ startedStep.from == KeyguardState.AOD
+ ) {
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.LAST_VALUE
}
}
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index efb604d..f10327e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -22,17 +22,17 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,20 +45,23 @@
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.OCCLUDED,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForOccludedToLockscreenOrHub()
listenForOccludedToDreaming()
- listenForOccludedToAodOrDozing()
+ listenForOccludedToAsleep()
listenForOccludedToGone()
listenForOccludedToAlternateBouncer()
listenForOccludedToPrimaryBouncer()
@@ -90,43 +93,72 @@
}
private fun listenForOccludedToLockscreenOrHub() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop
+ .filter { onTop -> !onTop }
+ .sample(
+ startedKeyguardState,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (_, startedState, isIdleOnCommunal) ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (startedState == KeyguardState.OCCLUDED) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded
+ .sample(
+ keyguardInteractor.isKeyguardShowing,
+ startedKeyguardTransitionStep,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal)
+ ->
+ // Occlusion signals come from the framework, and should interrupt any
+ // existing transition
+ if (
+ !isOccluded &&
+ isShowing &&
+ lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+ ) {
+ val to =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(to)
+ }
+ }
+ }
+ }
+ }
+
+ private fun listenForOccludedToGone() {
+ if (KeyguardWmStateRefactor.isEnabled) {
+ // We don't think OCCLUDED to GONE is possible. You should always have to go via a
+ // *_BOUNCER state to end up GONE. Launching an activity over a dismissable keyguard
+ // dismisses it, and even "extend unlock" doesn't unlock the device in the background.
+ // If we're wrong - sorry, add it back here.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardOccluded
.sample(
keyguardInteractor.isKeyguardShowing,
startedKeyguardTransitionStep,
- communalInteractor.isIdleOnCommunal,
- )
- .collect { (isOccluded, isShowing, lastStartedKeyguardState, isIdleOnCommunal) ->
- // Occlusion signals come from the framework, and should interrupt any
- // existing transition
- if (
- !isOccluded &&
- isShowing &&
- lastStartedKeyguardState.to == KeyguardState.OCCLUDED
- ) {
- val to =
- if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(to)
- }
- }
- }
- }
-
- private fun listenForOccludedToGone() {
- scope.launch {
- keyguardInteractor.isKeyguardOccluded
- .sample(
- combine(
- keyguardInteractor.isKeyguardShowing,
- startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
)
.collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
// Occlusion signals come from the framework, and should interrupt any
@@ -142,25 +174,12 @@
}
}
- private fun listenForOccludedToAodOrDozing() {
- scope.launch {
- powerInteractor.isAsleep
- .sample(
- combine(
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Pair
- ),
- ::toTriple
- )
- .collect { (isAsleep, lastStartedStep, isAodAvailable) ->
- if (lastStartedStep.to == KeyguardState.OCCLUDED && isAsleep) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
- }
- }
- }
+ fun dismissToGone() {
+ scope.launch { startTransitionTo(KeyguardState.GONE) }
+ }
+
+ private fun listenForOccludedToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.OCCLUDED) }
}
private fun listenForOccludedToAlternateBouncer() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index c5a2846..391dccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -31,7 +31,6 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
-import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import com.android.wm.shell.animation.Interpolators
@@ -42,6 +41,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@@ -59,18 +59,21 @@
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val selectedUserInteractor: SelectedUserInteractor,
- private val powerInteractor: PowerInteractor,
+ powerInteractor: PowerInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
TransitionInteractor(
fromState = KeyguardState.PRIMARY_BOUNCER,
transitionInteractor = transitionInteractor,
mainDispatcher = mainDispatcher,
bgDispatcher = bgDispatcher,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
) {
override fun start() {
listenForPrimaryBouncerToGone()
- listenForPrimaryBouncerToAodOrDozing()
+ listenForPrimaryBouncerToAsleep()
listenForPrimaryBouncerToLockscreenHubOrOccluded()
listenForPrimaryBouncerToDreamingLockscreenHosted()
listenForTransitionToCamera(scope, keyguardInteractor)
@@ -128,72 +131,86 @@
}
private fun listenForPrimaryBouncerToLockscreenHubOrOccluded() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- powerInteractor.isAwake,
- startedKeyguardTransitionStep,
- keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isDreaming,
- keyguardInteractor.isActiveDreamLockscreenHosted,
- communalInteractor.isIdleOnCommunal,
- )
- .collect {
- (
- isBouncerShowing,
- isAwake,
- lastStartedTransitionStep,
- occluded,
- isDreaming,
- isActiveDreamLockscreenHosted,
- isIdleOnCommunal) ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAwake &&
- !isActiveDreamLockscreenHosted
- ) {
- val toState =
- if (occluded && !isDreaming) {
- KeyguardState.OCCLUDED
- } else if (isIdleOnCommunal) {
- KeyguardState.GLANCEABLE_HUB
- } else if (isDreaming) {
- KeyguardState.DREAMING
- } else {
- KeyguardState.LOCKSCREEN
- }
- startTransitionTo(toState)
+ if (KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ startedKeyguardTransitionStep,
+ powerInteractor.isAwake,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal
+ )
+ .filter { (_, startedStep, _, _) ->
+ startedStep.to == KeyguardState.PRIMARY_BOUNCER
}
- }
+ .collect {
+ (
+ isBouncerShowing,
+ _,
+ isAwake,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !maybeStartTransitionToOccludedOrInsecureCamera() &&
+ !isBouncerShowing &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
+ }
+ }
+ } else {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(
+ powerInteractor.isAwake,
+ startedKeyguardTransitionStep,
+ keyguardInteractor.isKeyguardOccluded,
+ keyguardInteractor.isDreaming,
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ communalInteractor.isIdleOnCommunal,
+ )
+ .collect {
+ (
+ isBouncerShowing,
+ isAwake,
+ lastStartedTransitionStep,
+ occluded,
+ isDreaming,
+ isActiveDreamLockscreenHosted,
+ isIdleOnCommunal) ->
+ if (
+ !isBouncerShowing &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
+ isAwake &&
+ !isActiveDreamLockscreenHosted
+ ) {
+ val toState =
+ if (occluded && !isDreaming) {
+ KeyguardState.OCCLUDED
+ } else if (isIdleOnCommunal) {
+ KeyguardState.GLANCEABLE_HUB
+ } else if (isDreaming) {
+ KeyguardState.DREAMING
+ } else {
+ KeyguardState.LOCKSCREEN
+ }
+ startTransitionTo(toState)
+ }
+ }
+ }
}
}
- private fun listenForPrimaryBouncerToAodOrDozing() {
- scope.launch {
- keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- powerInteractor.isAsleep,
- startedKeyguardTransitionStep,
- keyguardInteractor.isAodAvailable,
- ::Triple
- ),
- ::toQuad
- )
- .collect { (isBouncerShowing, isAsleep, lastStartedTransitionStep, isAodAvailable)
- ->
- if (
- !isBouncerShowing &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER &&
- isAsleep
- ) {
- startTransitionTo(
- if (isAodAvailable) KeyguardState.AOD else KeyguardState.DOZING
- )
- }
- }
- }
+ private fun listenForPrimaryBouncerToAsleep() {
+ scope.launch { listenForSleepTransition(from = KeyguardState.PRIMARY_BOUNCER) }
}
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
index e5bb5a0..b9ec58c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractor.kt
@@ -38,6 +38,7 @@
val alpha: Flow<Float> = repository.bottomAreaAlpha
/** The position of the keyguard clock. */
private val _clockPosition = MutableStateFlow(Position(0, 0))
+ /** See [ClockSection] */
@Deprecated("with migrateClocksToBlueprint()")
val clockPosition: Flow<Position> = _clockPosition.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fbf1e22..143edf9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -36,6 +36,7 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
@@ -162,15 +163,18 @@
.distinctUntilChanged()
/** Whether the keyguard is showing or not. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
/** Whether the keyguard is dismissible or not. */
val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible
/** Whether the keyguard is occluded (covered by an activity). */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
/** Whether the keyguard is going away. */
+ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Keyguard can be clipped at the top as the shade is dragged */
@@ -265,8 +269,11 @@
configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up)
.flatMapLatest { translationDistance ->
- shadeRepository.legacyShadeExpansion.map {
- if (it == 0f) {
+ combine(
+ shadeRepository.legacyShadeExpansion.onStart { emit(0f) },
+ keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
+ ) { legacyShadeExpansion, goneValue ->
+ if (goneValue == 1f || legacyShadeExpansion == 0f) {
// Reset the translation value
0f
} else {
@@ -274,11 +281,12 @@
MathUtils.lerp(
translationDistance,
0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it)
+ Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion)
)
}
}
}
+ .distinctUntilChanged()
val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
new file mode 100644
index 0000000..9aa2202
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Logic related to keyguard occlusion. The keyguard is occluded when an activity with
+ * FLAG_SHOW_WHEN_LOCKED is on top of the activity task stack, with that activity displaying on top
+ * of ("occluding") the lockscreen UI. Common examples of this are Google Maps Navigation and the
+ * secure camera.
+ *
+ * This should usually be used only by keyguard internal classes. Most System UI use cases should
+ * use [KeyguardTransitionInteractor] to see if we're in [KeyguardState.OCCLUDED] instead.
+ */
+@SysUISingleton
+class KeyguardOcclusionInteractor
+@Inject
+constructor(
+ @Application scope: CoroutineScope,
+ val repository: KeyguardOcclusionRepository,
+ val powerInteractor: PowerInteractor,
+ val transitionInteractor: KeyguardTransitionInteractor,
+ val keyguardInteractor: KeyguardInteractor,
+) {
+ val showWhenLockedActivityInfo = repository.showWhenLockedActivityInfo.asStateFlow()
+
+ /**
+ * Whether a SHOW_WHEN_LOCKED activity is on top of the task stack. This does not necessarily
+ * mean we're OCCLUDED, as we could be GONE (unlocked), with an activity that can (but is not
+ * currently) displaying over the lockscreen.
+ *
+ * Transition interactors use this to determine when we should transition to the OCCLUDED state.
+ *
+ * Outside of the transition/occlusion interactors, you almost certainly don't want to use this.
+ * Instead, use KeyguardTransitionInteractor to figure out if we're in KeyguardState.OCCLUDED.
+ */
+ val isShowWhenLockedActivityOnTop = showWhenLockedActivityInfo.map { it.isOnTop }
+
+ /** Whether we should start a transition due to the power button launch gesture. */
+ fun shouldTransitionFromPowerButtonGesture(): Boolean {
+ // powerButtonLaunchGestureTriggered remains true while we're awake from a power button
+ // gesture. Check that we were asleep or transitioning to asleep before starting a
+ // transition, to ensure we don't transition while moving between, for example,
+ // *_BOUNCER -> LOCKSCREEN.
+ return powerInteractor.detailedWakefulness.value.powerButtonLaunchGestureTriggered &&
+ KeyguardState.deviceIsAsleepInState(transitionInteractor.getStartedState())
+ }
+
+ /**
+ * Whether the SHOW_WHEN_LOCKED activity was launched from the double tap power button gesture.
+ * This remains true while the activity is running and emits false once it is killed.
+ */
+ val showWhenLockedActivityLaunchedFromPowerGesture =
+ merge(
+ // Emit true when the power launch gesture is triggered, since this means a
+ // SHOW_WHEN_LOCKED activity will be launched from the gesture (unless we're
+ // currently
+ // GONE, in which case we're going back to GONE and launching the insecure camera).
+ powerInteractor.detailedWakefulness
+ .sample(transitionInteractor.currentKeyguardState, ::Pair)
+ .map { (wakefulness, currentKeyguardState) ->
+ wakefulness.powerButtonLaunchGestureTriggered &&
+ currentKeyguardState != KeyguardState.GONE
+ },
+ // Emit false once that activity goes away.
+ isShowWhenLockedActivityOnTop.filter { !it }.map { false }
+ )
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Whether launching an occluding activity will automatically dismiss keyguard. This happens if
+ * the keyguard is dismissable.
+ */
+ val occludingActivityWillDismissKeyguard =
+ keyguardInteractor.isKeyguardDismissible.stateIn(scope, SharingStarted.Eagerly, false)
+
+ /**
+ * Called to let System UI know that WM says a SHOW_WHEN_LOCKED activity is on top (or no longer
+ * on top).
+ *
+ * This signal arrives from WM when a SHOW_WHEN_LOCKED activity is started or killed - it is
+ * never set directly by System UI. While we might be the reason the activity was started
+ * (launching the camera from the power button gesture), we ultimately only receive this signal
+ * once that activity starts. It's up to us to start the appropriate keyguard transitions,
+ * because that activity is going to be visible (or not) regardless.
+ */
+ fun setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop: Boolean,
+ taskInfo: RunningTaskInfo? = null
+ ) {
+ repository.setShowWhenLockedActivityInfo(showWhenLockedActivityOnTop, taskInfo)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index b0a3881..8eb1a50 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -55,6 +56,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -66,6 +68,7 @@
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
+ private val shadeInteractor: ShadeInteractor,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
@@ -100,9 +103,10 @@
quickAffordanceAlwaysVisible(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
+ shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && !isUserInLockdown) {
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index d81f1f1..c28e49d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -41,6 +41,7 @@
private val powerInteractor: PowerInteractor,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val shadeInteractor: ShadeInteractor,
+ private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
fun start() {
@@ -91,6 +92,12 @@
}
scope.launch {
+ keyguardInteractor.isKeyguardDismissible.collect {
+ logger.log(TAG, VERBOSE, "isKeyguardDismissable", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.isAbleToDream.collect {
logger.log(TAG, VERBOSE, "isAbleToDream", it)
}
@@ -125,5 +132,11 @@
logger.log(TAG, VERBOSE, "onCameraLaunchDetected", it)
}
}
+
+ scope.launch {
+ keyguardOcclusionInteractor.showWhenLockedActivityInfo.collect {
+ logger.log(TAG, VERBOSE, "showWhenLockedActivityInfo", it)
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 37b331c..00902b4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
@@ -42,6 +43,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -56,11 +58,15 @@
@Inject
constructor(
@Application val scope: CoroutineScope,
+ private val keyguardRepository: KeyguardRepository,
private val repository: KeyguardTransitionRepository,
private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
private val fromPrimaryBouncerTransitionInteractor:
dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
private val fromAodTransitionInteractor: dagger.Lazy<FromAodTransitionInteractor>,
+ private val fromAlternateBouncerTransitionInteractor:
+ dagger.Lazy<FromAlternateBouncerTransitionInteractor>,
+ private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -207,6 +213,12 @@
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
+ /** Which keyguard state to use when the device goes to sleep. */
+ val asleepKeyguardState: StateFlow<KeyguardState> =
+ keyguardRepository.isAodAvailable
+ .map { aodAvailable -> if (aodAvailable) AOD else DOZING }
+ .stateIn(scope, SharingStarted.Eagerly, DOZING)
+
/**
* A pair of the most recent STARTED step, and the transition step immediately preceding it. The
* transition framework enforces that the previous step is either a CANCELED or FINISHED step,
@@ -368,7 +380,10 @@
when (val startedState = startedKeyguardState.replayCache.last()) {
LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ ALTERNATE_BOUNCER ->
+ fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer()
AOD -> fromAodTransitionInteractor.get().dismissAod()
+ DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing()
else ->
Log.e(
"KeyguardTransitionInteractor",
@@ -421,12 +436,17 @@
fromStatePredicate: (KeyguardState) -> Boolean,
toStatePredicate: (KeyguardState) -> Boolean,
): Flow<Boolean> {
+ return isInTransitionWhere { from, to -> fromStatePredicate(from) && toStatePredicate(to) }
+ }
+
+ fun isInTransitionWhere(
+ fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean
+ ): Flow<Boolean> {
return repository.transitions
.filter { it.transitionState != TransitionState.CANCELED }
.mapLatest {
it.transitionState != TransitionState.FINISHED &&
- fromStatePredicate(it.from) &&
- toStatePredicate(it.to)
+ fromToStatePredicate(it.from, it.to)
}
.distinctUntilChanged()
}
@@ -447,4 +467,16 @@
*/
fun isFinishedInStateWhereValue(stateMatcher: (KeyguardState) -> Boolean) =
stateMatcher(finishedKeyguardState.replayCache.last())
+
+ fun getCurrentState(): KeyguardState {
+ return currentKeyguardState.replayCache.last()
+ }
+
+ fun getStartedState(): KeyguardState {
+ return startedKeyguardState.replayCache.last()
+ }
+
+ fun getFinishedState(): KeyguardState {
+ return finishedKeyguardState.replayCache.last()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 4d731ec..8905c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -43,7 +43,6 @@
private val scrimLogger: ScrimLogger,
private val powerInteractor: PowerInteractor,
) {
-
init {
listenForStartedKeyguardTransitionStep()
}
@@ -52,9 +51,7 @@
scope.launch {
transitionInteractor.startedKeyguardTransitionStep.collect {
scrimLogger.d(TAG, "listenForStartedKeyguardTransitionStep", it)
- lightRevealScrimRepository.startRevealAmountAnimator(
- willBeRevealedInState(it.to),
- )
+ lightRevealScrimRepository.startRevealAmountAnimator(willBeRevealedInState(it.to))
}
}
}
@@ -89,25 +86,25 @@
companion object {
- /**
- * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
- * state after the transition is complete. If false, scrim will be fully hidden.
- */
- private fun willBeRevealedInState(state: KeyguardState): Boolean {
- return when (state) {
- KeyguardState.OFF -> false
- KeyguardState.DOZING -> false
- KeyguardState.AOD -> false
- KeyguardState.DREAMING -> true
- KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
- KeyguardState.GLANCEABLE_HUB -> true
- KeyguardState.ALTERNATE_BOUNCER -> true
- KeyguardState.PRIMARY_BOUNCER -> true
- KeyguardState.LOCKSCREEN -> true
- KeyguardState.GONE -> true
- KeyguardState.OCCLUDED -> true
+ /**
+ * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given
+ * state after the transition is complete. If false, scrim will be fully hidden.
+ */
+ private fun willBeRevealedInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> false
+ KeyguardState.DOZING -> false
+ KeyguardState.AOD -> false
+ KeyguardState.DREAMING -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> true
+ KeyguardState.OCCLUDED -> true
+ }
}
- }
val TAG = LightRevealScrimInteractor::class.simpleName!!
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 3ccbdba..375df3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -18,15 +18,20 @@
import android.animation.ValueAnimator
import android.util.Log
+import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.sample
import java.util.UUID
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -46,6 +51,8 @@
val transitionInteractor: KeyguardTransitionInteractor,
val mainDispatcher: CoroutineDispatcher,
val bgDispatcher: CoroutineDispatcher,
+ val powerInteractor: PowerInteractor,
+ val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) {
val name = this::class.simpleName ?: "UnknownTransitionInteractor"
abstract val transitionRepository: KeyguardTransitionRepository
@@ -65,7 +72,11 @@
suspend fun startTransitionTo(
toState: KeyguardState,
animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState),
- modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE
+ modeOnCanceled: TransitionModeOnCanceled = TransitionModeOnCanceled.LAST_VALUE,
+ // Even more information about why the owner started this transition, if this is a dangerous
+ // transition (*cough* occlusion) where you'd be sad to not have all the info you can get in
+ // a bugreport.
+ ownerReason: String = "",
): UUID? {
if (
fromState != transitionInteractor.startedKeyguardState.replayCache.last() &&
@@ -85,7 +96,7 @@
return withContext(mainDispatcher) {
transitionRepository.startTransition(
TransitionInfo(
- name,
+ name + if (ownerReason.isNotBlank()) "($ownerReason)" else "",
fromState,
toState,
animator,
@@ -95,24 +106,107 @@
}
}
+ /**
+ * Check whether we need to transition to [KeyguardState.OCCLUDED], based on the presence of a
+ * SHOW_WHEN_LOCKED activity, or back to [KeyguardState.GONE], for some power button launch
+ * gesture cases. If so, start the transition.
+ *
+ * Returns true if a transition was started, false otherwise.
+ */
+ suspend fun maybeStartTransitionToOccludedOrInsecureCamera(): Boolean {
+ if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
+ if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
+ // If the current state is GONE when the launch gesture is triggered, it means we
+ // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
+ // second tap indicates that the user's intent was actually to launch the unlocked
+ // (insecure) camera, so we should transition back to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture while GONE"
+ )
+ } else if (keyguardOcclusionInteractor.occludingActivityWillDismissKeyguard.value) {
+ // The double tap gesture occurred while not GONE (AOD/LOCKSCREEN/etc.), but the
+ // keyguard is dismissable. The activity launch will dismiss the keyguard, so we
+ // should transition to GONE.
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "Power button gesture on dismissable keyguard"
+ )
+ } else {
+ // Otherwise, the double tap gesture occurred while not GONE and not dismissable,
+ // which means we will launch the secure camera, which OCCLUDES the keyguard.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "Power button gesture on lockscreen"
+ )
+ }
+
+ return true
+ } else if (keyguardOcclusionInteractor.showWhenLockedActivityInfo.value.isOnTop) {
+ // A SHOW_WHEN_LOCKED activity is on top of the task stack. Transition to OCCLUDED so
+ // it's visible.
+ // TODO(b/307976454) - Centralize transition to DREAMING here.
+ startTransitionTo(
+ KeyguardState.OCCLUDED,
+ ownerReason = "SHOW_WHEN_LOCKED activity on top"
+ )
+
+ return true
+ } else {
+ // No transition needed, let the interactor figure out where to go.
+ return false
+ }
+ }
+
+ /**
+ * Transition to the appropriate state when the device goes to sleep while in [from].
+ *
+ * We could also just use [fromState], but it's more readable in the From*TransitionInteractor
+ * if you're explicitly declaring which state you're listening from. If you passed in the wrong
+ * state, [startTransitionTo] would complain anyway.
+ */
+ suspend fun listenForSleepTransition(
+ from: KeyguardState,
+ modeOnCanceledFromStartedStep: (TransitionStep) -> TransitionModeOnCanceled = {
+ TransitionModeOnCanceled.LAST_VALUE
+ }
+ ) {
+ powerInteractor.isAsleep
+ .filter { isAsleep -> isAsleep }
+ .sample(startedKeyguardTransitionStep)
+ .filter { startedStep -> startedStep.to == from }
+ .map(modeOnCanceledFromStartedStep)
+ .collect { modeOnCanceled ->
+ startTransitionTo(
+ toState = transitionInteractor.asleepKeyguardState.value,
+ modeOnCanceled = modeOnCanceled,
+ ownerReason = "Sleep transition triggered"
+ )
+ }
+ }
+
/** This signal may come in before the occlusion signal, and can provide a custom transition */
fun listenForTransitionToCamera(
scope: CoroutineScope,
keyguardInteractor: KeyguardInteractor,
) {
- scope.launch {
- keyguardInteractor.onCameraLaunchDetected
- .sample(transitionInteractor.finishedKeyguardState)
- .collect { finishedKeyguardState ->
- // Other keyguard state transitions may trigger on the first power button push,
- // so use the last finishedKeyguardState to determine the overriding FROM state
- if (finishedKeyguardState == fromState) {
- startTransitionTo(
- toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
- )
+ if (!KeyguardWmStateRefactor.isEnabled) {
+ scope.launch {
+ keyguardInteractor.onCameraLaunchDetected
+ .sample(transitionInteractor.finishedKeyguardState)
+ .collect { finishedKeyguardState ->
+ // Other keyguard state transitions may trigger on the first power button
+ // push,
+ // so use the last finishedKeyguardState to determine the overriding FROM
+ // state
+ if (finishedKeyguardState == fromState) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
+ }
}
- }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 462d5e6..4812e03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -42,7 +42,7 @@
import kotlinx.coroutines.launch
private const val TAG = "KeyguardBlueprintViewBinder"
-private const val DEBUG = true
+private const val DEBUG = false
@SysUISingleton
class KeyguardBlueprintViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index dc1f33d..fc95ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -73,7 +73,6 @@
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -148,6 +147,7 @@
viewModel.alpha(viewState).collect { alpha ->
view.alpha = alpha
childViews[statusViewId]?.alpha = alpha
+ childViews[burnInLayerId]?.alpha = alpha
}
}
}
@@ -195,66 +195,68 @@
// large clock isn't added to burnInLayer due to its scale transition
// so we also need to add translation to it here
// same as translationX
- burnInParams
- .flatMapLatest { params -> viewModel.translationY(params) }
- .collect { y ->
- childViews[burnInLayerId]?.translationY = y
- childViews[largeClockId]?.translationY = y
- childViews[aodNotificationIconContainerId]?.translationY = y
- }
+ viewModel.translationY.collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ childViews[largeClockId]?.translationY = y
+ childViews[aodNotificationIconContainerId]?.translationY = y
+ }
}
launch {
- burnInParams
- .flatMapLatest { params -> viewModel.translationX(params) }
- .collect { state ->
- val px = state.value ?: return@collect
- when {
- state.isToOrFrom(KeyguardState.AOD) -> {
- childViews[largeClockId]?.translationX = px
- childViews[burnInLayerId]?.translationX = px
- childViews[aodNotificationIconContainerId]
- ?.translationX = px
- }
- state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
- for ((key, childView) in childViews.entries) {
- when (key) {
- indicationArea,
- startButton,
- endButton,
- lockIcon -> {
- // Do not move these views
- }
- else -> childView.translationX = px
+ viewModel.translationX.collect { state ->
+ val px = state.value ?: return@collect
+ when {
+ state.isToOrFrom(KeyguardState.AOD) -> {
+ childViews[largeClockId]?.translationX = px
+ childViews[burnInLayerId]?.translationX = px
+ childViews[aodNotificationIconContainerId]?.translationX =
+ px
+ }
+ state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+ for ((key, childView) in childViews.entries) {
+ when (key) {
+ indicationArea,
+ startButton,
+ endButton,
+ lockIcon -> {
+ // Do not move these views
}
+ else -> childView.translationX = px
}
}
}
}
+ }
}
launch {
- burnInParams
- .flatMapLatest { params -> viewModel.scale(params) }
- .collect { scaleViewModel ->
- if (scaleViewModel.scaleClockOnly) {
- // For clocks except weather clock, we have scale transition
- // besides translate
- childViews[largeClockId]?.let {
- it.scaleX = scaleViewModel.scale
- it.scaleY = scaleViewModel.scale
- }
- } else {
- // For weather clock, large clock should have only scale
- // transition with other parts in burnInLayer
- childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
- childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
- childViews[aodNotificationIconContainerId]?.scaleX =
- scaleViewModel.scale
- childViews[aodNotificationIconContainerId]?.scaleY =
- scaleViewModel.scale
+ viewModel.scale.collect { scaleViewModel ->
+ if (scaleViewModel.scaleClockOnly) {
+ // For clocks except weather clock, we have scale transition
+ // besides translate
+ childViews[largeClockId]?.let {
+ it.scaleX = scaleViewModel.scale
+ it.scaleY = scaleViewModel.scale
}
+ // Make sure to reset these views, or they will be invisible
+ if (childViews[burnInLayerId]?.scaleX != 1f) {
+ childViews[burnInLayerId]?.scaleX = 1f
+ childViews[burnInLayerId]?.scaleY = 1f
+ childViews[aodNotificationIconContainerId]?.scaleX = 1f
+ childViews[aodNotificationIconContainerId]?.scaleY = 1f
+ view.requestLayout()
+ }
+ } else {
+ // For weather clock, large clock should have only scale
+ // transition with other parts in burnInLayer
+ childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+ childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleX =
+ scaleViewModel.scale
+ childViews[aodNotificationIconContainerId]?.scaleY =
+ scaleViewModel.scale
}
+ }
}
if (NotificationIconContainerRefactor.isEnabled) {
@@ -311,6 +313,8 @@
}
}
+ launch { burnInParams.collect { viewModel.updateBurnInParams(it) } }
+
if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
launch {
deviceEntryHapticsInteractor.playSuccessHaptic.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index e67a324..88ce9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -39,29 +39,24 @@
private val clockViewModel: KeyguardClockViewModel,
) : KeyguardSection() {
private lateinit var burnInLayer: AodBurnInLayer
- private lateinit var emptyView: View
+ // The burn-in layer requires at least 1 view at all times
+ private val emptyView: View by lazy {
+ View(context, null).apply {
+ id = R.id.burn_in_layer_empty_view
+ visibility = View.GONE
+ }
+ }
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
return
}
- // The burn-in layer requires at least 1 view at all times
- emptyView =
- View(context, null).apply {
- id = R.id.burn_in_layer_empty_view
- visibility = View.GONE
- }
constraintLayout.addView(emptyView)
burnInLayer =
AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
registerListener(rootView)
addView(emptyView)
- if (!migrateClocksToBlueprint()) {
- val statusView =
- constraintLayout.requireViewById<View>(R.id.keyguard_status_view)
- addView(statusView)
- }
}
constraintLayout.addView(burnInLayer)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 4ddd039..6184c82 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -199,7 +199,7 @@
private val TRANSITION_PROPERTIES =
arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS)
- private val DEBUG = true
+ private val DEBUG = false
private val TAG = VisibilityBoundsTransition::class.simpleName!!
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index 62fc1da..f961e08 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -65,17 +65,10 @@
) {
private val TAG = "AodBurnInViewModel"
- /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
- fun translationX(
- params: BurnInParameters,
- ): Flow<Float> {
- return burnIn(params).map { it.translationX.toFloat() }
- }
-
- /** Vertical translation for elements that need to apply anti-burn-in tactics. */
- fun translationY(
+ /** All burn-in movement: x,y,scale, to shift items and prevent burn-in */
+ fun movement(
burnInParams: BurnInParameters,
- ): Flow<Float> {
+ ): Flow<BurnInModel> {
val params =
if (burnInParams.minViewY < burnInParams.topInset) {
// minViewY should never be below the inset. Correct it if needed
@@ -84,13 +77,12 @@
} else {
burnInParams
}
-
return configurationInteractor
.dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
.flatMapLatest { enterFromTopAmount ->
combine(
keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
- burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+ burnIn(params).onStart { emit(BurnInModel()) },
goneToAodTransitionViewModel
.enterFromTopTranslationY(enterFromTopAmount)
.onStart { emit(StateToValue()) },
@@ -100,32 +92,26 @@
aodToLockscreenTransitionViewModel.translationY(params.translationY).onStart {
emit(StateToValue())
},
- ) { keyguardTranslationY, burnInY, goneToAod, occludedToLockscreen, aodToLockscreen
- ->
- if (isInTransition(aodToLockscreen.transitionState)) {
- aodToLockscreen.value ?: 0f
- } else if (isInTransition(goneToAod.transitionState)) {
- (goneToAod.value ?: 0f) + burnInY
- } else {
- burnInY + occludedToLockscreen + keyguardTranslationY
- }
+ ) {
+ keyguardTranslationY,
+ burnInModel,
+ goneToAod,
+ occludedToLockscreen,
+ aodToLockscreen ->
+ val translationY =
+ if (isInTransition(aodToLockscreen.transitionState)) {
+ aodToLockscreen.value ?: 0f
+ } else if (isInTransition(goneToAod.transitionState)) {
+ (goneToAod.value ?: 0f) + burnInModel.translationY
+ } else {
+ burnInModel.translationY + occludedToLockscreen + keyguardTranslationY
+ }
+ burnInModel.copy(translationY = translationY.toInt())
}
}
.distinctUntilChanged()
}
- /** Scale for elements that need to apply anti-burn-in tactics. */
- fun scale(
- params: BurnInParameters,
- ): Flow<BurnInScaleViewModel> {
- return burnIn(params).map {
- BurnInScaleViewModel(
- scale = it.scale,
- scaleClockOnly = it.scaleClockOnly,
- )
- }
- }
-
private fun isInTransition(state: TransitionState): Boolean {
return state == STARTED || state == RUNNING
}
@@ -140,7 +126,7 @@
burnInInteractor.burnIn(
xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
yDimenResourceId = R.dimen.burn_in_prevention_offset_y
- )
+ ),
) { interpolated, burnIn ->
val useScaleOnly =
(clockController(params.clockControllerProvider)
@@ -164,7 +150,6 @@
} else {
max(params.topInset, params.minViewY + burnInY) - params.minViewY
}
-
BurnInModel(
translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
translationY = translationY,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
index a9eec18..7e39a88 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt
@@ -43,6 +43,10 @@
to = KeyguardState.GONE,
)
+ /**
+ * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake
+ * and unlock, and also during insecure camera launch (which is GONE -> AOD (canceled) -> GONE).
+ */
fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
var startAlpha = 1f
return transitionAnimation.sharedFlow(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
index 105a7ed..445575f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt
@@ -16,12 +16,15 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.util.MathUtils
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
/** Breaks down AOD->OCCLUDED transition into discrete steps for corresponding views to consume. */
@SysUISingleton
@@ -37,5 +40,23 @@
to = KeyguardState.OCCLUDED,
)
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to AOD, the second cancels that transition and starts AOD -> OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 31e8093..bd19c80 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -180,7 +180,7 @@
}
private val isUnlocked: Flow<Boolean> =
- deviceEntryInteractor.isUnlocked.flatMapLatest { isUnlocked ->
+ keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked ->
if (!isUnlocked) {
flowOf(false)
} else {
@@ -197,7 +197,7 @@
val iconType: Flow<DeviceEntryIconView.IconType> =
combine(
deviceEntryUdfpsInteractor.isListeningForUdfps,
- keyguardInteractor.isKeyguardDismissible,
+ isUnlocked,
) { isListeningForUdfps, isUnlocked ->
if (isListeningForUdfps) {
DeviceEntryIconView.IconType.FINGERPRINT
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
new file mode 100644
index 0000000..c0b1195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
+import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Breaks down DOZING->OCCLUDED transition into discrete steps for corresponding views to consume.
+ */
+@SysUISingleton
+class DozingToOccludedTransitionViewModel
+@Inject
+constructor(
+ animationFlow: KeyguardTransitionAnimationFlow,
+) : DeviceEntryIconTransition {
+ private val transitionAnimation =
+ animationFlow.setup(
+ duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION,
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+
+ /**
+ * Fade out the lockscreen during a transition to OCCLUDED.
+ *
+ * This happens when pressing the power button while a SHOW_WHEN_LOCKED activity is on the top
+ * of the task stack, as well as when the power button is double tapped on the LOCKSCREEN (the
+ * first tap transitions to DOZING, the second cancels that transition and starts DOZING ->
+ * OCCLUDED.
+ */
+ fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+ var currentAlpha = 0f
+ return transitionAnimation.sharedFlow(
+ duration = 250.milliseconds,
+ startTime = 100.milliseconds, // Wait for the light reveal to "hit" the LS elements.
+ onStart = { currentAlpha = viewState.alpha() },
+ onStep = { MathUtils.lerp(currentAlpha, 0f, it) },
+ onCancel = { 0f },
+ )
+ }
+
+ override val deviceEntryParentViewAlpha = transitionAnimation.immediatelyTransitionTo(0f)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 38d5e0f..5ca9215 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -24,9 +24,11 @@
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -47,8 +49,11 @@
import com.android.systemui.util.ui.zip
import javax.inject.Inject
import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -57,12 +62,14 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardRootViewModel
@Inject
constructor(
+ @Application private val scope: CoroutineScope,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val keyguardInteractor: KeyguardInteractor,
@@ -73,8 +80,11 @@
AlternateBouncerToGoneTransitionViewModel,
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToGoneTransitionViewModel: DozingToGoneTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
+ private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
@@ -99,6 +109,8 @@
private val aodAlphaViewModel: AodAlphaViewModel,
private val shadeInteractor: ShadeInteractor,
) {
+ private var burnInJob: Job? = null
+ private val burnInModel = MutableStateFlow(BurnInModel())
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardState
@@ -169,8 +181,11 @@
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToGoneTransitionViewModel.lockscreenAlpha(viewState),
aodToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToGoneTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
+ dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
goneToAodTransitionViewModel.enterFromTopAnimationAlpha,
goneToDozingTransitionViewModel.lockscreenAlpha,
@@ -207,21 +222,33 @@
/** For elements that appear and move during the animation -> AOD */
val burnInLayerAlpha: Flow<Float> = aodAlphaViewModel.alpha
- fun translationY(params: BurnInParameters): Flow<Float> {
- return aodBurnInViewModel.translationY(params)
- }
+ val translationY: Flow<Float> = burnInModel.map { it.translationY.toFloat() }
- fun translationX(params: BurnInParameters): Flow<StateToValue> {
- return merge(
- aodBurnInViewModel.translationX(params).map { StateToValue(to = AOD, value = it) },
+ val translationX: Flow<StateToValue> =
+ merge(
+ burnInModel.map { StateToValue(to = AOD, value = it.translationX.toFloat()) },
lockscreenToGlanceableHubTransitionViewModel.keyguardTranslationX,
glanceableHubToLockscreenTransitionViewModel.keyguardTranslationX,
)
+
+ fun updateBurnInParams(params: BurnInParameters) {
+ burnInJob?.cancel()
+
+ burnInJob =
+ scope.launch {
+ aodBurnInViewModel.movement(params).collect {
+ burnInModel.value = it
+ }
+ }
}
- fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
- return aodBurnInViewModel.scale(params)
- }
+ val scale: Flow<BurnInScaleViewModel> =
+ burnInModel.map {
+ BurnInScaleViewModel(
+ scale = it.scale,
+ scaleClockOnly = it.scaleClockOnly,
+ )
+ }
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 840b309..26c63f3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -109,7 +109,7 @@
import com.android.systemui.media.controls.util.MediaFlags;
import com.android.systemui.media.controls.util.MediaUiEventLogger;
import com.android.systemui.media.controls.util.SmallHash;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.monet.ColorScheme;
import com.android.systemui.monet.Style;
import com.android.systemui.plugins.ActivityStarter;
@@ -223,7 +223,7 @@
protected int mUid = Process.INVALID_UID;
private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final FalsingManager mFalsingManager;
private MetadataAnimationHandler mMetadataAnimationHandler;
private ColorSchemeTransition mColorSchemeTransition;
@@ -304,7 +304,7 @@
MediaViewController mediaViewController,
SeekBarViewModel seekBarViewModel,
Lazy<MediaDataManager> lazyMediaDataManager,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
MediaCarouselController mediaCarouselController,
FalsingManager falsingManager,
SystemClock systemClock,
@@ -324,7 +324,7 @@
mSeekBarViewModel = seekBarViewModel;
mMediaViewController = mediaViewController;
mMediaDataManagerLazy = lazyMediaDataManager;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mMediaCarouselController = mediaCarouselController;
mFalsingManager = falsingManager;
mSystemClock = systemClock;
@@ -737,7 +737,7 @@
mPackageName, mMediaViewHolder.getSeamlessButton());
} else {
mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
} else {
@@ -761,7 +761,7 @@
}
}
} else {
- mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaOutputDialogManager.createAndShow(mPackageName, true,
mMediaViewHolder.getSeamlessButton());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
index 5d113a9..452cb7e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt
@@ -31,7 +31,8 @@
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String?): LocalMediaManager {
- return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager)
- .run { LocalMediaManager(context, localBluetoothManager, this, packageName) }
+ return InfoMediaManager.createInstance(context, packageName, localBluetoothManager).run {
+ LocalMediaManager(context, localBluetoothManager, this, packageName)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
deleted file mode 100644
index b6e3937..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ /dev/null
@@ -1,83 +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 com.android.systemui.media.dialog
-
-import android.app.KeyguardManager
-import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
-import android.view.View
-import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.systemui.animation.DialogTransitionAnimator
-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.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import javax.inject.Inject
-
-/**
- * Factory to create [MediaOutputBroadcastDialog] objects.
- */
-class MediaOutputBroadcastDialogFactory @Inject constructor(
- private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
- private val uiEventLogger: UiEventLogger,
- private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
-) {
- var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
-
- /** Creates a [MediaOutputBroadcastDialog] for the given package. */
- fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- // Dismiss the previous dialog, if any.
- mediaOutputBroadcastDialog?.dismiss()
-
- val controller = MediaOutputController(context, packageName,
- mediaSessionManager, lbm, starter, notifCollection,
- dialogTransitionAnimator, nearbyMediaDevicesManager, audioManager,
- powerExemptionManager, keyGuardManager, featureFlags, userTracker)
- val dialog =
- MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
- mediaOutputBroadcastDialog = dialog
-
- // Show the dialog.
- if (view != null) {
- dialogTransitionAnimator.showFromView(dialog, view)
- } else {
- dialog.show()
- }
- }
-
- /** dismiss [MediaOutputBroadcastDialog] if exist. */
- fun dismiss() {
- mediaOutputBroadcastDialog?.dismiss()
- mediaOutputBroadcastDialog = null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
new file mode 100644
index 0000000..54d175c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dialog
+
+import android.content.Context
+import android.view.View
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.broadcast.BroadcastSender
+import javax.inject.Inject
+
+/** Manager to create and show a [MediaOutputBroadcastDialog]. */
+class MediaOutputBroadcastDialogManager
+@Inject
+constructor(
+ private val context: Context,
+ private val broadcastSender: BroadcastSender,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ private val mediaOutputControllerFactory: MediaOutputController.Factory
+) {
+ var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
+
+ /** Creates a [MediaOutputBroadcastDialog] for the given package. */
+ fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ // Dismiss the previous dialog, if any.
+ mediaOutputBroadcastDialog?.dismiss()
+
+ val controller = mediaOutputControllerFactory.create(packageName)
+ val dialog =
+ MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
+ mediaOutputBroadcastDialog = dialog
+
+ // Show the dialog.
+ if (view != null) {
+ dialogTransitionAnimator.showFromView(dialog, view)
+ } else {
+ dialog.show()
+ }
+ }
+
+ /** dismiss [MediaOutputBroadcastDialog] if exist. */
+ fun dismiss() {
+ mediaOutputBroadcastDialog?.dismiss()
+ mediaOutputBroadcastDialog = null
+ }
+}
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 b3b7bce..adee7f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -64,6 +64,7 @@
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.core.graphics.drawable.IconCompat;
@@ -91,6 +92,10 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
@@ -105,8 +110,6 @@
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
-import javax.inject.Inject;
-
/**
* Controller for media output dialog
*/
@@ -170,10 +173,13 @@
ACTION_BROADCAST_INFO_ICON
}
- @Inject
- public MediaOutputController(@NonNull Context context, String packageName,
- MediaSessionManager mediaSessionManager, LocalBluetoothManager
- lbm, ActivityStarter starter,
+ @AssistedInject
+ public MediaOutputController(
+ Context context,
+ @Assisted String packageName,
+ MediaSessionManager mediaSessionManager,
+ @Nullable LocalBluetoothManager lbm,
+ ActivityStarter starter,
CommonNotifCollection notifCollection,
DialogTransitionAnimator dialogTransitionAnimator,
NearbyMediaDevicesManager nearbyMediaDevicesManager,
@@ -193,7 +199,7 @@
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, null, lbm);
+ InfoMediaManager imm = InfoMediaManager.createInstance(mContext, packageName, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogTransitionAnimator = dialogTransitionAnimator;
@@ -222,6 +228,12 @@
R.dimen.media_output_dialog_selectable_margin_end);
}
+ @AssistedFactory
+ public interface Factory {
+ /** Construct a MediaOutputController */
+ MediaOutputController create(String packageName);
+ }
+
protected void start(@NonNull Callback cb) {
synchronized (mMediaDevicesLock) {
mCachedMediaDevices.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
rename to packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
index 02be0c1..e7816a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt
@@ -16,43 +16,24 @@
package com.android.systemui.media.dialog
-import android.app.KeyguardManager
import android.content.Context
-import android.media.AudioManager
-import android.media.session.MediaSessionManager
-import android.os.PowerExemptionManager
import android.view.View
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
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.settings.UserTracker
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import javax.inject.Inject
-/** Factory to create [MediaOutputDialog] objects. */
-open class MediaOutputDialogFactory
+/** Manager to create and show a [MediaOutputDialog]. */
+open class MediaOutputDialogManager
@Inject
constructor(
private val context: Context,
- private val mediaSessionManager: MediaSessionManager,
- private val lbm: LocalBluetoothManager?,
- private val starter: ActivityStarter,
private val broadcastSender: BroadcastSender,
- private val notifCollection: CommonNotifCollection,
private val uiEventLogger: UiEventLogger,
private val dialogTransitionAnimator: DialogTransitionAnimator,
- private val nearbyMediaDevicesManager: NearbyMediaDevicesManager,
- private val audioManager: AudioManager,
- private val powerExemptionManager: PowerExemptionManager,
- private val keyGuardManager: KeyguardManager,
- private val featureFlags: FeatureFlags,
- private val userTracker: UserTracker
+ private val mediaOutputControllerFactory: MediaOutputController.Factory,
) {
companion object {
const val INTERACTION_JANK_TAG = "media_output"
@@ -60,8 +41,8 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
- createWithController(
+ open fun createAndShow(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+ createAndShowWithController(
packageName,
aboveStatusBar,
controller =
@@ -78,12 +59,12 @@
}
/** Creates a [MediaOutputDialog] for the given package. */
- open fun createWithController(
+ open fun createAndShowWithController(
packageName: String,
aboveStatusBar: Boolean,
controller: DialogTransitionAnimator.Controller?,
) {
- create(
+ createAndShow(
packageName,
aboveStatusBar,
dialogTransitionAnimatorController = controller,
@@ -91,8 +72,10 @@
)
}
- open fun createDialogForSystemRouting(controller: DialogTransitionAnimator.Controller? = null) {
- create(
+ open fun createAndShowForSystemRouting(
+ controller: DialogTransitionAnimator.Controller? = null
+ ) {
+ createAndShow(
packageName = null,
aboveStatusBar = false,
dialogTransitionAnimatorController = null,
@@ -100,7 +83,7 @@
)
}
- private fun create(
+ private fun createAndShow(
packageName: String?,
aboveStatusBar: Boolean,
dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?,
@@ -109,23 +92,9 @@
// Dismiss the previous dialog, if any.
mediaOutputDialog?.dismiss()
- val controller =
- MediaOutputController(
- context,
- packageName,
- mediaSessionManager,
- lbm,
- starter,
- notifCollection,
- dialogTransitionAnimator,
- nearbyMediaDevicesManager,
- audioManager,
- powerExemptionManager,
- keyGuardManager,
- featureFlags,
- userTracker
- )
- val dialog =
+ val controller = mediaOutputControllerFactory.create(packageName)
+
+ val mediaOutputDialog =
MediaOutputDialog(
context,
aboveStatusBar,
@@ -135,16 +104,15 @@
uiEventLogger,
includePlaybackAndAppMetadata
)
- mediaOutputDialog = dialog
// Show the dialog.
if (dialogTransitionAnimatorController != null) {
dialogTransitionAnimator.show(
- dialog,
+ mediaOutputDialog,
dialogTransitionAnimatorController,
)
} else {
- dialog.show()
+ mediaOutputDialog.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
index 38d31ed..774792f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt
@@ -31,8 +31,8 @@
* BroadcastReceiver for handling media output intent
*/
class MediaOutputDialogReceiver @Inject constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val mediaOutputBroadcastDialogFactory: MediaOutputBroadcastDialogFactory
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
+ private val mediaOutputBroadcastDialogManager: MediaOutputBroadcastDialogManager
) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
@@ -42,7 +42,7 @@
launchMediaOutputDialogIfPossible(packageName)
}
MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG -> {
- mediaOutputDialogFactory.createDialogForSystemRouting()
+ mediaOutputDialogManager.createAndShowForSystemRouting()
}
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG -> {
if (!legacyLeAudioSharing()) return
@@ -55,7 +55,7 @@
private fun launchMediaOutputDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputDialogFactory.create(packageName, false)
+ mediaOutputDialogManager.createAndShow(packageName, false)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.")
}
@@ -63,7 +63,7 @@
private fun launchMediaOutputBroadcastDialogIfPossible(packageName: String?) {
if (!packageName.isNullOrEmpty()) {
- mediaOutputBroadcastDialogFactory.create(
+ mediaOutputBroadcastDialogManager.createAndShow(
packageName, aboveStatusBar = true, view = null)
} else if (DEBUG) {
Log.e(TAG, "Unable to launch media output broadcast dialog. Package name is empty.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
index b5b1f0f..6e7e0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java
@@ -34,15 +34,15 @@
private static final String TAG = "MediaOutputSwitcherDialogUI";
private final CommandQueue mCommandQueue;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
@Inject
public MediaOutputSwitcherDialogUI(
Context context,
CommandQueue commandQueue,
- MediaOutputDialogFactory mediaOutputDialogFactory) {
+ MediaOutputDialogManager mediaOutputDialogManager) {
mCommandQueue = commandQueue;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
}
@Override
@@ -54,7 +54,7 @@
@MainThread
public void showMediaOutputSwitcher(String packageName) {
if (!TextUtils.isEmpty(packageName)) {
- mMediaOutputDialogFactory.create(packageName, false, null);
+ mMediaOutputDialogManager.createAndShow(packageName, false, null);
} else {
Log.e(TAG, "Unable to launch media output dialog. Package name is empty.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
deleted file mode 100644
index fc45228..0000000
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialog.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.mediaprojection.devicepolicy
-
-import android.content.Context
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.phone.SystemUIDialog
-
-/** Dialog that shows that screen capture is disabled on this device. */
-class ScreenCaptureDisabledDialog(context: Context) : SystemUIDialog(context) {
-
- init {
- setTitle(context.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
- setMessage(
- context.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
- )
- setIcon(R.drawable.ic_cast)
- setButton(BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ -> cancel() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
new file mode 100644
index 0000000..8aed535
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDisabledDialogDelegate.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.mediaprojection.devicepolicy
+
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import javax.inject.Inject
+
+/** Dialog that shows that screen capture is disabled on this device. */
+class ScreenCaptureDisabledDialogDelegate @Inject constructor(
+ @Main private val resources: Resources,
+ private val systemUIDialogFactory: SystemUIDialog.Factory
+) : SystemUIDialog.Delegate {
+
+ override fun createDialog(): SystemUIDialog {
+ val dialog = systemUIDialogFactory.create(this)
+ dialog.setTitle(resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_title))
+ dialog.setMessage(
+ resources.getString(R.string.screen_capturing_disabled_by_policy_dialog_description)
+ )
+ dialog.setIcon(R.drawable.ic_cast)
+ dialog.setButton(BUTTON_POSITIVE, resources.getString(android.R.string.ok)) {
+ _, _ -> dialog.cancel()
+ }
+
+ return dialog
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index 8b034b2..17f9caf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -59,7 +59,7 @@
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.AlertDialogWithDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -79,6 +79,7 @@
private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
private final StatusBarManager mStatusBarManager;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
private String mPackageName;
private int mUid;
@@ -93,14 +94,17 @@
private boolean mUserSelectingTask = false;
@Inject
- public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
+ public MediaProjectionPermissionActivity(
+ FeatureFlags featureFlags,
Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
StatusBarManager statusBarManager,
- MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
+ MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate) {
mFeatureFlags = featureFlags;
mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
mStatusBarManager = statusBarManager;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
}
@Override
@@ -315,10 +319,7 @@
final UserHandle hostUserHandle = getHostUserHandle();
if (mScreenCaptureDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(hostUserHandle)) {
- // Using application context for the dialog, instead of the activity context, so we get
- // the correct screen width when in split screen.
- Context dialogContext = getApplicationContext();
- AlertDialog dialog = new ScreenCaptureDisabledDialog(dialogContext);
+ AlertDialog dialog = mScreenCaptureDisabledDialogDelegate.createDialog();
setUpDialog(dialog);
dialog.show();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
index cc8cc51..d6629e0 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModel.kt
@@ -18,14 +18,18 @@
import android.app.ActivityManager.RunningTaskInfo
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.withContext
class TaskSwitcherNotificationViewModel
@@ -36,21 +40,30 @@
) {
val uiState: Flow<TaskSwitcherNotificationUiState> =
- interactor.taskSwitchChanges.map { taskSwitchChange ->
- Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
- when (taskSwitchChange) {
- is TaskSwitchState.TaskSwitched -> {
- TaskSwitcherNotificationUiState.Showing(
- projectedTask = taskSwitchChange.projectedTask,
- foregroundTask = taskSwitchChange.foregroundTask,
- )
- }
- is TaskSwitchState.NotProjectingTask,
- is TaskSwitchState.TaskUnchanged -> {
- TaskSwitcherNotificationUiState.NotShowing
+ interactor.taskSwitchChanges
+ .map { taskSwitchChange ->
+ Log.d(TAG, "taskSwitchChange: $taskSwitchChange")
+ when (taskSwitchChange) {
+ is TaskSwitchState.TaskSwitched -> {
+ TaskSwitcherNotificationUiState.Showing(
+ projectedTask = taskSwitchChange.projectedTask,
+ foregroundTask = taskSwitchChange.foregroundTask,
+ )
+ }
+ is TaskSwitchState.NotProjectingTask,
+ is TaskSwitchState.TaskUnchanged -> {
+ TaskSwitcherNotificationUiState.NotShowing
+ }
}
}
- }
+ .transformLatest { uiState ->
+ emit(uiState)
+ if (uiState is TaskSwitcherNotificationUiState.Showing) {
+ delay(NOTIFICATION_MAX_SHOW_DURATION)
+ Log.d(TAG, "Auto hiding notification after $NOTIFICATION_MAX_SHOW_DURATION")
+ emit(TaskSwitcherNotificationUiState.NotShowing)
+ }
+ }
suspend fun onSwitchTaskClicked(task: RunningTaskInfo) {
interactor.switchProjectedTask(task)
@@ -60,6 +73,7 @@
withContext(backgroundDispatcher) { interactor.goBackToTask(task) }
companion object {
+ @VisibleForTesting val NOTIFICATION_MAX_SHOW_DURATION = 5.seconds
private const val TAG = "TaskSwitchNotifVM"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 152f193..9f7d1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -19,6 +19,7 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR;
+
import static com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler.DEBUG_MISSING_GESTURE_TAG;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
@@ -28,7 +29,6 @@
import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.Trace;
import android.os.UserHandle;
@@ -70,9 +70,11 @@
import java.io.PrintWriter;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
+
@SysUISingleton
public class NavigationBarControllerImpl implements
ConfigurationController.ConfigurationListener,
@@ -82,7 +84,7 @@
private static final String TAG = NavigationBarControllerImpl.class.getSimpleName();
private final Context mContext;
- private final Handler mHandler;
+ private final Executor mExecutor;
private final NavigationBarComponent.Factory mNavigationBarComponentFactory;
private final SecureSettings mSecureSettings;
private final DisplayTracker mDisplayTracker;
@@ -119,7 +121,7 @@
NavigationModeController navigationModeController,
SysUiState sysUiFlagsContainer,
CommandQueue commandQueue,
- @Main Handler mainHandler,
+ @Main Executor mainExecutor,
ConfigurationController configurationController,
NavBarHelper navBarHelper,
TaskbarDelegate taskbarDelegate,
@@ -133,7 +135,7 @@
SecureSettings secureSettings,
DisplayTracker displayTracker) {
mContext = context;
- mHandler = mainHandler;
+ mExecutor = mainExecutor;
mNavigationBarComponentFactory = navigationBarComponentFactory;
mSecureSettings = secureSettings;
mDisplayTracker = displayTracker;
@@ -193,7 +195,7 @@
mNavMode = mode;
updateAccessibilityButtonModeIfNeeded();
- mHandler.post(() -> {
+ mExecutor.execute(() -> {
// create/destroy nav bar based on nav mode only in unfolded state
if (oldMode != mNavMode) {
updateNavbarForTaskbar();
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index c0e688f..c7aae3c 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -23,6 +23,7 @@
import android.app.role.RoleManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
@@ -37,20 +38,21 @@
interface NoteTaskModule {
@[Binds IntoMap ClassKey(NoteTaskControllerUpdateService::class)]
- fun NoteTaskControllerUpdateService.bindNoteTaskControllerUpdateService(): Service
+ fun bindNoteTaskControllerUpdateService(service: NoteTaskControllerUpdateService): Service
- @[Binds IntoMap ClassKey(NoteTaskBubblesController.NoteTaskBubblesService::class)]
- fun NoteTaskBubblesController.NoteTaskBubblesService.bindNoteTaskBubblesService(): Service
+ @[Binds IntoMap ClassKey(NoteTaskBubblesService::class)]
+ fun bindNoteTaskBubblesService(service: NoteTaskBubblesService): Service
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
- fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
+ fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity
@[Binds IntoMap ClassKey(LaunchNotesRoleSettingsTrampolineActivity::class)]
- fun LaunchNotesRoleSettingsTrampolineActivity.bindLaunchNotesRoleSettingsTrampolineActivity():
- Activity
+ fun bindLaunchNotesRoleSettingsTrampolineActivity(
+ activity: LaunchNotesRoleSettingsTrampolineActivity
+ ): Activity
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
- fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
+ fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
index 2d63dbc..7d3f2a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceModule.kt
@@ -25,5 +25,7 @@
interface NoteTaskQuickAffordanceModule {
@[Binds IntoSet]
- fun NoteTaskQuickAffordanceConfig.bindNoteTaskQuickAffordance(): KeyguardQuickAffordanceConfig
+ fun bindNoteTaskQuickAffordance(
+ impl: NoteTaskQuickAffordanceConfig
+ ): KeyguardQuickAffordanceConfig
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
index e1d1ec2..5432793d 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt
@@ -20,20 +20,24 @@
* the [KeyguardTransitionInteractor].
*/
internal val internalWakefulnessState: WakefulnessState = WakefulnessState.AWAKE,
-
val lastWakeReason: WakeSleepReason = WakeSleepReason.OTHER,
val lastSleepReason: WakeSleepReason = WakeSleepReason.OTHER,
- /**
+ /**
* Whether the power button double tap gesture was triggered since the last time went to sleep.
* If this value is true while [isAsleep]=true, it means we'll be waking back up shortly. If it
* is true while [isAwake]=true, it means we're awake because of the button gesture.
*
- * This value remains true until the next time [isAsleep]=true.
+ * This value remains true until the next time [isAsleep]=true, since it would otherwise be
+ * totally arbitrary at what point we decide the gesture was no longer "triggered". Since a
+ * sleep event is guaranteed to arrive prior to the next power button gesture (as the first tap
+ * of the double tap always begins a sleep transition), this will always be reset to false prior
+ * to a subsequent power gesture.
*/
val powerButtonLaunchGestureTriggered: Boolean = false,
) {
- fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE ||
+ fun isAwake() =
+ internalWakefulnessState == WakefulnessState.AWAKE ||
internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE
fun isAsleep() = !isAwake()
@@ -48,11 +52,10 @@
fun isAsleepFrom(wakeSleepReason: WakeSleepReason) =
isAsleep() && lastSleepReason == wakeSleepReason
- fun isAwakeOrAsleepFrom(reason: WakeSleepReason) =
- isAsleepFrom(reason) || isAwakeFrom(reason)
+ fun isAwakeOrAsleepFrom(reason: WakeSleepReason) = isAsleepFrom(reason) || isAwakeFrom(reason)
fun isAwakeFromTapOrGesture(): Boolean {
- return isAwake() && (lastWakeReason == WakeSleepReason.TAP ||
- lastWakeReason == WakeSleepReason.GESTURE)
+ return isAwake() &&
+ (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 20f3c4d..bd66843 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -103,14 +103,27 @@
public override fun handleClick(view: View?) {
if (isRecording) {
isRecording = false
- stopScreenRecord()
+ stopIssueRecordingService()
} else {
mUiHandler.post { showPrompt(view) }
}
refreshState()
}
- private fun stopScreenRecord() =
+ private fun startIssueRecordingService(screenRecord: Boolean, winscopeTracing: Boolean) =
+ PendingIntent.getForegroundService(
+ userContextProvider.userContext,
+ RecordingService.REQUEST_CODE,
+ IssueRecordingService.getStartIntent(
+ userContextProvider.userContext,
+ screenRecord,
+ winscopeTracing
+ ),
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
+
+ private fun stopIssueRecordingService() =
PendingIntent.getService(
userContextProvider.userContext,
RecordingService.REQUEST_CODE,
@@ -124,6 +137,7 @@
delegateFactory
.create {
isRecording = true
+ startIssueRecordingService(it.screenRecord, it.winscopeTracing)
refreshState()
}
.createDialog()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index 0dd0a60..52cf4ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -481,7 +481,8 @@
mSecondaryMobileTitleText.setTextAppearance(
R.style.TextAppearance_InternetDialog_Active);
- TextView mSecondaryMobileSummaryText = mDialogView.requireViewById(R.id.secondary_mobile_summary);
+ TextView mSecondaryMobileSummaryText =
+ mDialogView.requireViewById(R.id.secondary_mobile_summary);
summary = getMobileNetworkSummary(autoSwitchNonDdsSubId);
if (!TextUtils.isEmpty(summary)) {
mSecondaryMobileSummaryText.setText(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
new file mode 100644
index 0000000..22bbbbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileDataInteractor.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.util.kotlin.combine
+import com.android.systemui.util.kotlin.getBatteryLevel
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isDevicePluggedIn
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+
+/** Observes BatterySaver mode state changes providing the [BatterySaverTileModel.Standard]. */
+open class BatterySaverTileDataInteractor
+@Inject
+constructor(
+ @Background private val bgCoroutineContext: CoroutineContext,
+ private val batteryController: BatteryController,
+) : QSTileDataInteractor<BatterySaverTileModel> {
+
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<BatterySaverTileModel> =
+ combine(
+ batteryController.isDevicePluggedIn().distinctUntilChanged().flowOn(bgCoroutineContext),
+ batteryController
+ .isBatteryPowerSaveEnabled()
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext),
+ batteryController.getBatteryLevel().distinctUntilChanged().flowOn(bgCoroutineContext),
+ ) {
+ isPluggedIn: Boolean,
+ isPowerSaverEnabled: Boolean,
+ _, // we are only interested in battery level change, not the actual level
+ ->
+ BatterySaverTileModel.Standard(isPluggedIn, isPowerSaverEnabled)
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
new file mode 100644
index 0000000..1e4eb38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Handles airplane mode tile clicks and long clicks. */
+class BatterySaverTileUserActionInteractor
+@Inject
+constructor(
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+ private val batteryController: BatteryController
+) : QSTileUserActionInteractor<BatterySaverTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<BatterySaverTileModel>) =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ if (!data.isPluggedIn) {
+ batteryController.setPowerSaveMode(!data.isPowerSaving, action.view)
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
new file mode 100644
index 0000000..dbec50d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/model/BatterySaverTileModel.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.domain.model
+
+/** BatterySaver mode tile model. */
+sealed interface BatterySaverTileModel {
+
+ val isPluggedIn: Boolean
+ val isPowerSaving: Boolean
+
+ /** For when the device does not support extreme battery saver mode. */
+ data class Standard(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ ) : BatterySaverTileModel
+
+ /**
+ * For when device supports extreme battery saver mode. Whether or not that mode is enabled is
+ * determined through [isExtremeSaving].
+ */
+ data class Extreme(
+ override val isPluggedIn: Boolean,
+ override val isPowerSaving: Boolean,
+ val isExtremeSaving: Boolean,
+ ) : BatterySaverTileModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
new file mode 100644
index 0000000..0c08fba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.battery.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.battery.domain.model.BatterySaverTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [BatterySaverTileModel] to [QSTileState]. */
+open class BatterySaverTileMapper
+@Inject
+constructor(
+ @Main protected val resources: Resources,
+ private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<BatterySaverTileModel> {
+
+ override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.battery_detail_switch_title)
+ contentDescription = label
+
+ icon = {
+ Icon.Loaded(
+ resources.getDrawable(
+ if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
+ else R.drawable.qs_battery_saver_icon_off,
+ theme
+ ),
+ null
+ )
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.None
+
+ if (data.isPluggedIn) {
+ activationState = QSTileState.ActivationState.UNAVAILABLE
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ } else if (data.isPowerSaving) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+
+ if (data is BatterySaverTileModel.Extreme) {
+ secondaryLabel =
+ resources.getString(
+ if (data.isExtremeSaving) R.string.extreme_battery_saver_text
+ else R.string.standard_battery_saver_text
+ )
+ stateDescription = secondaryLabel
+ } else {
+ secondaryLabel = ""
+ }
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ secondaryLabel = ""
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
new file mode 100644
index 0000000..caae4d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain
+
+import android.content.Context
+import android.content.res.Resources
+import android.widget.Switch
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [InternetTileModel] to [QSTileState]. */
+class InternetTileMapper
+@Inject
+constructor(
+ @Main private val resources: Resources,
+ private val theme: Resources.Theme,
+ private val context: Context,
+) : QSTileDataToStateMapper<InternetTileModel> {
+
+ override fun map(config: QSTileConfig, data: InternetTileModel): QSTileState =
+ QSTileState.build(resources, theme, config.uiConfig) {
+ label = resources.getString(R.string.quick_settings_internet_label)
+ expandedAccessibilityClass = Switch::class
+
+ if (data.secondaryLabel != null) {
+ secondaryLabel = data.secondaryLabel.loadText(context)
+ } else {
+ secondaryLabel = data.secondaryTitle
+ }
+
+ stateDescription = data.stateDescription.loadContentDescription(context)
+ contentDescription = data.contentDescription.loadContentDescription(context)
+
+ if (data.icon != null) {
+ this.icon = { data.icon }
+ } else if (data.iconId != null) {
+ val loadedIcon =
+ Icon.Loaded(
+ resources.getDrawable(data.iconId!!, theme),
+ contentDescription = null
+ )
+ this.icon = { loadedIcon }
+ }
+
+ sideViewIcon = QSTileState.SideViewIcon.Chevron
+
+ activationState =
+ if (data is InternetTileModel.Active) QSTileState.ActivationState.ACTIVE
+ else QSTileState.ActivationState.INACTIVE
+
+ supportedActions =
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
new file mode 100644
index 0000000..fdc596b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileDataInteractor.kt
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.annotation.StringRes
+import android.content.Context
+import android.os.UserHandle
+import android.text.Html
+import com.android.settingslib.graph.SignalDrawable
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository
+import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+
+@OptIn(ExperimentalCoroutinesApi::class)
+/** Observes internet state changes providing the [InternetTileModel]. */
+class InternetTileDataInteractor
+@Inject
+constructor(
+ private val context: Context,
+ @Application private val scope: CoroutineScope,
+ airplaneModeRepository: AirplaneModeRepository,
+ private val connectivityRepository: ConnectivityRepository,
+ ethernetInteractor: EthernetInteractor,
+ mobileIconsInteractor: MobileIconsInteractor,
+ wifiInteractor: WifiInteractor,
+) : QSTileDataInteractor<InternetTileModel> {
+ private val internetLabel: String = context.getString(R.string.quick_settings_internet_label)
+
+ // Three symmetrical Flows that can be switched upon based on the value of
+ // [DefaultConnectionModel]
+ private val wifiIconFlow: Flow<InternetTileModel> =
+ wifiInteractor.wifiNetwork.flatMapLatest {
+ val wifiIcon = WifiIcon.fromModel(it, context, showHotspotInfo = true)
+ if (it is WifiNetworkModel.Active && wifiIcon is WifiIcon.Visible) {
+ val secondary = removeDoubleQuotes(it.ssid)
+ flowOf(
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = Icon.Loaded(context.getDrawable(wifiIcon.icon.res)!!, null),
+ stateDescription = wifiIcon.contentDescription,
+ contentDescription = ContentDescription.Loaded("$internetLabel,$secondary"),
+ )
+ )
+ } else {
+ notConnectedFlow
+ }
+ }
+
+ private val mobileDataContentName: Flow<CharSequence?> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ flowOf(null)
+ } else {
+ combine(it.isRoaming, it.networkTypeIconGroup) { isRoaming, networkTypeIconGroup ->
+ val cd = loadString(networkTypeIconGroup.contentDescription)
+ if (isRoaming) {
+ val roaming = context.getString(R.string.data_connection_roaming)
+ if (cd != null) {
+ context.getString(R.string.mobile_data_text_format, roaming, cd)
+ } else {
+ roaming
+ }
+ } else {
+ cd
+ }
+ }
+ }
+ }
+
+ private val mobileIconFlow: Flow<InternetTileModel> =
+ mobileIconsInteractor.activeDataIconInteractor.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ combine(
+ it.networkName,
+ it.signalLevelIcon,
+ mobileDataContentName,
+ ) { networkNameModel, signalIcon, dataContentDescription ->
+ when (signalIcon) {
+ is SignalIconModel.Cellular -> {
+ val secondary =
+ mobileDataContentConcat(
+ networkNameModel.name,
+ dataContentDescription
+ )
+
+ val stateLevel = signalIcon.level
+ val drawable = SignalDrawable(context)
+ drawable.setLevel(stateLevel)
+ val loadedIcon = Icon.Loaded(drawable, null)
+
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ icon = loadedIcon,
+ stateDescription = ContentDescription.Loaded(secondary.toString()),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ is SignalIconModel.Satellite -> {
+ val secondary =
+ signalIcon.icon.contentDescription.loadContentDescription(context)
+ InternetTileModel.Active(
+ secondaryTitle = secondary,
+ iconId = signalIcon.icon.res,
+ stateDescription = ContentDescription.Loaded(secondary),
+ contentDescription = ContentDescription.Loaded(internetLabel),
+ )
+ }
+ }
+ }
+ }
+ }
+
+ private fun mobileDataContentConcat(
+ networkName: String?,
+ dataContentDescription: CharSequence?
+ ): CharSequence {
+ if (dataContentDescription == null) {
+ return networkName ?: ""
+ }
+ if (networkName == null) {
+ return Html.fromHtml(dataContentDescription.toString(), 0)
+ }
+
+ return Html.fromHtml(
+ context.getString(
+ R.string.mobile_carrier_text_format,
+ networkName,
+ dataContentDescription
+ ),
+ 0
+ )
+ }
+
+ private fun loadString(@StringRes resId: Int): CharSequence? =
+ if (resId != 0) {
+ context.getString(resId)
+ } else {
+ null
+ }
+
+ private val ethernetIconFlow: Flow<InternetTileModel> =
+ ethernetInteractor.icon.flatMapLatest {
+ if (it == null) {
+ notConnectedFlow
+ } else {
+ val secondary = it.contentDescription
+ flowOf(
+ InternetTileModel.Active(
+ secondaryLabel = secondary?.toText(),
+ iconId = it.res,
+ stateDescription = null,
+ contentDescription = secondary,
+ )
+ )
+ }
+ }
+
+ private val notConnectedFlow: StateFlow<InternetTileModel> =
+ combine(
+ wifiInteractor.areNetworksAvailable,
+ airplaneModeRepository.isAirplaneMode,
+ ) { networksAvailable, isAirplaneMode ->
+ when {
+ isAirplaneMode -> {
+ val secondary = context.getString(R.string.status_bar_airplane)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription = ContentDescription.Loaded(secondary),
+ )
+ }
+ networksAvailable -> {
+ val secondary =
+ context.getString(R.string.quick_settings_networks_available)
+ InternetTileModel.Inactive(
+ secondaryTitle = secondary,
+ iconId = R.drawable.ic_qs_no_internet_available,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Loaded("$internetLabel,$secondary")
+ )
+ }
+ else -> {
+ NOT_CONNECTED_NETWORKS_UNAVAILABLE
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), NOT_CONNECTED_NETWORKS_UNAVAILABLE)
+
+ /**
+ * Consumable flow describing the correct state for the InternetTile.
+ *
+ * Strict ordering of which repo is sending its data to the internet tile. Swaps between each of
+ * the interim providers (wifi, mobile, ethernet, or not-connected).
+ */
+ override fun tileData(
+ user: UserHandle,
+ triggers: Flow<DataUpdateTrigger>
+ ): Flow<InternetTileModel> =
+ connectivityRepository.defaultConnections.flatMapLatest {
+ when {
+ it.ethernet.isDefault -> ethernetIconFlow
+ it.mobile.isDefault || it.carrierMerged.isDefault -> mobileIconFlow
+ it.wifi.isDefault -> wifiIconFlow
+ else -> notConnectedFlow
+ }
+ }
+
+ override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+ private companion object {
+ val NOT_CONNECTED_NETWORKS_UNAVAILABLE =
+ InternetTileModel.Inactive(
+ secondaryLabel = Text.Resource(R.string.quick_settings_networks_unavailable),
+ iconId = R.drawable.ic_qs_no_internet_unavailable,
+ stateDescription = null,
+ contentDescription =
+ ContentDescription.Resource(R.string.quick_settings_networks_unavailable),
+ )
+
+ fun removeDoubleQuotes(string: String?): String? {
+ if (string == null) return null
+ return if (string.firstOrNull() == '"' && string.lastOrNull() == '"') {
+ string.substring(1, string.length - 1)
+ } else string
+ }
+
+ fun ContentDescription.toText(): Text =
+ when (this) {
+ is ContentDescription.Loaded -> Text.Loaded(this.description)
+ is ContentDescription.Resource -> Text.Resource(this.res)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
new file mode 100644
index 0000000..2620cd5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.dialog.InternetDialogManager
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.connectivity.AccessPointController
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+/** Handles internet tile clicks. */
+class InternetTileUserActionInteractor
+@Inject
+constructor(
+ @Main private val mainContext: CoroutineContext,
+ private val internetDialogManager: InternetDialogManager,
+ private val accessPointController: AccessPointController,
+ private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<InternetTileModel> {
+
+ override suspend fun handleInput(input: QSTileInput<InternetTileModel>): Unit =
+ with(input) {
+ when (action) {
+ is QSTileUserAction.Click -> {
+ withContext(mainContext) {
+ internetDialogManager.create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ action.view,
+ )
+ }
+ }
+ is QSTileUserAction.LongClick -> {
+ qsTileIntentUserActionHandler.handle(
+ action.view,
+ Intent(Settings.ACTION_WIFI_SETTINGS)
+ )
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
new file mode 100644
index 0000000..ece90461
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/model/InternetTileModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.internet.domain.model
+
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+
+/** Model describing the state that the QS Internet tile should be in. */
+sealed interface InternetTileModel {
+ val secondaryTitle: CharSequence?
+ val secondaryLabel: Text?
+ val iconId: Int?
+ val icon: Icon?
+ val stateDescription: ContentDescription?
+ val contentDescription: ContentDescription?
+
+ data class Active(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+
+ data class Inactive(
+ override val secondaryTitle: CharSequence? = null,
+ override val secondaryLabel: Text? = null,
+ override val iconId: Int? = null,
+ override val icon: Icon? = null,
+ override val stateDescription: ContentDescription? = null,
+ override val contentDescription: ContentDescription? = null,
+ ) : InternetTileModel
+}
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
similarity index 67%
copy from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
copy to packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
index 838e41e..bb3b654 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,6 @@
* limitations under the License.
*/
-package android.hardware;
+package com.android.systemui.recordissue
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
-}
-
+data class IssueRecordingConfig(val screenRecord: Boolean, val winscopeTracing: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
index f487258..55d7f8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt
@@ -20,7 +20,9 @@
import android.content.Context
import android.content.Intent
import android.content.res.Resources
+import android.net.Uri
import android.os.Handler
+import android.os.UserHandle
import com.android.internal.logging.UiEventLogger
import com.android.systemui.dagger.qualifiers.LongRunning
import com.android.systemui.dagger.qualifiers.Main
@@ -30,6 +32,8 @@
import com.android.systemui.screenrecord.RecordingServiceStrings
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.traceur.FileSender
+import com.android.traceur.TraceUtils
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -60,9 +64,89 @@
override fun provideRecordingServiceStrings(): RecordingServiceStrings = IrsStrings(resources)
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ when (intent?.action) {
+ ACTION_START -> {
+ TraceUtils.traceStart(
+ contentResolver,
+ DEFAULT_TRACE_TAGS,
+ DEFAULT_BUFFER_SIZE,
+ DEFAULT_IS_INCLUDING_WINSCOPE,
+ DEFAULT_IS_INCLUDING_APP_TRACE,
+ DEFAULT_IS_LONG_TRACE,
+ DEFAULT_ATTACH_TO_BUGREPORT,
+ DEFAULT_MAX_TRACE_SIZE,
+ DEFAULT_MAX_TRACE_DURATION_IN_MINUTES
+ )
+ if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) {
+ // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action
+ // will circumvent the RecordingService's screen recording start code.
+ return super.onStartCommand(Intent(ACTION_SHOW_START_NOTIF), flags, startId)
+ }
+ }
+ ACTION_STOP,
+ ACTION_STOP_NOTIF -> {
+ TraceUtils.traceStop(contentResolver)
+ }
+ ACTION_SHARE -> {
+ shareRecording(intent)
+
+ // Unlike all other actions, action_share has different behavior for the screen
+ // recording qs tile than it does for the record issue qs tile. Return sticky to
+ // avoid running any of the base class' code for this action.
+ return START_STICKY
+ }
+ else -> {}
+ }
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ private fun shareRecording(intent: Intent) {
+ val files = TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).get()
+ val traceUris: MutableList<Uri> = FileSender.getUriForFiles(this, files, AUTHORITY)
+
+ if (
+ intent.hasExtra(EXTRA_PATH) && intent.getStringExtra(EXTRA_PATH)?.isNotEmpty() == true
+ ) {
+ traceUris.add(Uri.parse(intent.getStringExtra(EXTRA_PATH)))
+ }
+
+ val sendIntent =
+ FileSender.buildSendIntent(this, traceUris).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+
+ if (mNotificationId != NOTIF_BASE_ID) {
+ val currentUserId = mUserContextTracker.userContext.userId
+ mNotificationManager.cancelAsUser(null, mNotificationId, UserHandle(currentUserId))
+ }
+
+ // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity
+ mKeyguardDismissUtil.executeWhenUnlocked(
+ {
+ startActivity(sendIntent)
+ false
+ },
+ false,
+ false
+ )
+ }
+
companion object {
private const val TAG = "IssueRecordingService"
private const val CHANNEL_ID = "issue_record"
+ private const val EXTRA_SCREEN_RECORD = "extra_screenRecord"
+ private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing"
+
+ private val DEFAULT_TRACE_TAGS = listOf<String>()
+ private const val DEFAULT_BUFFER_SIZE = 16384
+ private const val DEFAULT_IS_INCLUDING_WINSCOPE = true
+ private const val DEFAULT_IS_LONG_TRACE = false
+ private const val DEFAULT_IS_INCLUDING_APP_TRACE = true
+ private const val DEFAULT_ATTACH_TO_BUGREPORT = true
+ private const val DEFAULT_MAX_TRACE_SIZE = 10240
+ private const val DEFAULT_MAX_TRACE_DURATION_IN_MINUTES = 30
+
+ private val TRACE_FILE_NAME = TraceUtils.getOutputFilename(TraceUtils.RecordingType.TRACE)
+ private const val AUTHORITY = "com.android.systemui.fileprovider"
/**
* Get an intent to stop the issue recording service.
@@ -71,7 +155,7 @@
* @return
*/
fun getStopIntent(context: Context): Intent =
- Intent(context, RecordingService::class.java)
+ Intent(context, IssueRecordingService::class.java)
.setAction(ACTION_STOP)
.putExtra(Intent.EXTRA_USER_HANDLE, context.userId)
@@ -80,8 +164,15 @@
*
* @param context Context from the requesting activity
*/
- fun getStartIntent(context: Context): Intent =
- Intent(context, RecordingService::class.java).setAction(ACTION_START)
+ fun getStartIntent(
+ context: Context,
+ screenRecord: Boolean,
+ winscopeTracing: Boolean
+ ): Intent =
+ Intent(context, IssueRecordingService::class.java)
+ .setAction(ACTION_START)
+ .putExtra(EXTRA_SCREEN_RECORD, screenRecord)
+ .putExtra(EXTRA_WINSCOPE_TRACING, winscopeTracing)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 80f11f1..ff18a11 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -17,8 +17,6 @@
package com.android.systemui.recordissue
import android.annotation.SuppressLint
-import android.app.BroadcastOptions
-import android.app.PendingIntent
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
@@ -40,11 +38,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
-import com.android.systemui.screenrecord.RecordingService
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -52,12 +48,12 @@
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.concurrent.Executor
+import java.util.function.Consumer
class RecordIssueDialogDelegate
@AssistedInject
constructor(
private val factory: SystemUIDialog.Factory,
- private val userContextProvider: UserContextProvider,
private val userTracker: UserTracker,
private val flags: FeatureFlagsClassic,
@Background private val bgExecutor: Executor,
@@ -65,14 +61,15 @@
private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
private val userFileManager: UserFileManager,
- @Assisted private val onStarted: Runnable,
+ private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate,
+ @Assisted private val onStarted: Consumer<IssueRecordingConfig>,
) : SystemUIDialog.Delegate {
/** To inject dependencies and allow for easier testing */
@AssistedFactory
interface Factory {
/** Create a dialog object */
- fun create(onStarted: Runnable): RecordIssueDialogDelegate
+ fun create(onStarted: Consumer<IssueRecordingConfig>): RecordIssueDialogDelegate
}
@SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
@@ -86,10 +83,12 @@
setIcon(R.drawable.qs_record_issue_icon_off)
setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
- onStarted.run()
- if (screenRecordSwitch.isChecked) {
- requestScreenCapture()
- }
+ onStarted.accept(
+ IssueRecordingConfig(
+ screenRecordSwitch.isChecked,
+ true /* TODO: Base this on issueType selected */
+ )
+ )
dismiss()
}
}
@@ -124,7 +123,7 @@
.isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
) {
mainExecutor.execute {
- ScreenCaptureDisabledDialog(context).show()
+ screenCaptureDisabledDialogDelegate.createDialog().show()
screenRecordSwitch.isChecked = false
}
return@execute
@@ -176,13 +175,4 @@
show()
}
}
-
- private fun requestScreenCapture() =
- PendingIntent.getForegroundService(
- userContextProvider.userContext,
- RecordingService.REQUEST_CODE,
- IssueRecordingService.getStartIntent(userContextProvider.userContext),
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
- )
- .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle())
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 306aad1..75bf131 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -156,6 +156,7 @@
* desired scene. Once enough of the transition has occurred, the [currentScene] will become
* [toScene] (unless the transition is canceled by user action or another call to this method).
*/
+ @JvmOverloads
fun changeScene(
toScene: SceneKey,
loggingReason: String,
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index a4ba2a2..8fe84c9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -42,11 +42,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.CallbackController;
import dagger.Lazy;
@@ -71,18 +69,19 @@
private CountDownTimer mCountDownTimer = null;
private final Executor mMainExecutor;
private final BroadcastDispatcher mBroadcastDispatcher;
- private final Context mContext;
private final FeatureFlags mFlags;
- private final UserContextProvider mUserContextProvider;
private final UserTracker mUserTracker;
private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
- private final SystemUIDialog.Factory mDialogFactory;
+ private final ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ private final ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ private final ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
protected static final String INTENT_UPDATE_STATE =
"com.android.systemui.screenrecord.UPDATE_STATE";
protected static final String EXTRA_STATE = "extra_state";
- private CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
+ private final CopyOnWriteArrayList<RecordingStateChangeCallback> mListeners =
new CopyOnWriteArrayList<>();
private final Lazy<ScreenCaptureDevicePolicyResolver> mDevicePolicyResolver;
@@ -115,24 +114,26 @@
* Create a new RecordingController
*/
@Inject
- public RecordingController(@Main Executor mainExecutor,
+ public RecordingController(
+ @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher,
- Context context,
FeatureFlags flags,
- UserContextProvider userContextProvider,
Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
UserTracker userTracker,
MediaProjectionMetricsLogger mediaProjectionMetricsLogger,
- SystemUIDialog.Factory dialogFactory) {
+ ScreenCaptureDisabledDialogDelegate screenCaptureDisabledDialogDelegate,
+ ScreenRecordDialogDelegate.Factory screenRecordDialogFactory,
+ ScreenRecordPermissionDialogDelegate.Factory
+ screenRecordPermissionDialogDelegateFactory) {
mMainExecutor = mainExecutor;
- mContext = context;
mFlags = flags;
mDevicePolicyResolver = devicePolicyResolver;
mBroadcastDispatcher = broadcastDispatcher;
- mUserContextProvider = userContextProvider;
mUserTracker = userTracker;
mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
- mDialogFactory = dialogFactory;
+ mScreenCaptureDisabledDialogDelegate = screenCaptureDisabledDialogDelegate;
+ mScreenRecordDialogFactory = screenRecordDialogFactory;
+ mScreenRecordPermissionDialogDelegateFactory = screenRecordPermissionDialogDelegateFactory;
BroadcastOptions options = BroadcastOptions.makeBasic();
options.setInteractive(true);
@@ -163,27 +164,18 @@
if (mFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES)
&& mDevicePolicyResolver.get()
.isScreenCaptureCompletelyDisabled(getHostUserHandle())) {
- return new ScreenCaptureDisabledDialog(mContext);
+ return mScreenCaptureDisabledDialogDelegate.createDialog();
}
mMediaProjectionMetricsLogger.notifyProjectionInitiated(
getHostUid(), SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
- return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
- ? mDialogFactory.create(new ScreenRecordPermissionDialogDelegate(
- getHostUserHandle(),
- getHostUid(),
- /* controller= */ this,
- activityStarter,
- mUserContextProvider,
- onStartRecordingClicked,
- mMediaProjectionMetricsLogger,
- mDialogFactory))
- : new ScreenRecordDialog(
- context,
- /* controller= */ this,
- mUserContextProvider,
- onStartRecordingClicked);
+ return (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
+ ? mScreenRecordPermissionDialogDelegateFactory
+ .create(this, getHostUserHandle(), getHostUid(), onStartRecordingClicked)
+ : mScreenRecordDialogFactory
+ .create(this, onStartRecordingClicked))
+ .createDialog();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index b355d2d..ac94f39 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -60,25 +60,27 @@
public static final int REQUEST_CODE = 2;
private static final int USER_ID_NOT_SPECIFIED = -1;
- private static final int NOTIF_BASE_ID = 4273;
+ protected static final int NOTIF_BASE_ID = 4273;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
private static final String GROUP_KEY = "screen_record_saved";
private static final String EXTRA_RESULT_CODE = "extra_resultCode";
- private static final String EXTRA_PATH = "extra_path";
+ protected static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
private static final String EXTRA_CAPTURE_TARGET = "extra_captureTarget";
protected static final String ACTION_START = "com.android.systemui.screenrecord.START";
+ protected static final String ACTION_SHOW_START_NOTIF =
+ "com.android.systemui.screenrecord.START_NOTIF";
protected static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
- private static final String ACTION_STOP_NOTIF =
+ protected static final String ACTION_STOP_NOTIF =
"com.android.systemui.screenrecord.STOP_FROM_NOTIF";
- private static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
+ protected static final String ACTION_SHARE = "com.android.systemui.screenrecord.SHARE";
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private final RecordingController mController;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
+ protected final KeyguardDismissUtil mKeyguardDismissUtil;
private final Handler mMainHandler;
private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
@@ -86,9 +88,9 @@
private ScreenMediaRecorder mRecorder;
private final Executor mLongExecutor;
private final UiEventLogger mUiEventLogger;
- private final NotificationManager mNotificationManager;
- private final UserContextProvider mUserContextTracker;
- private int mNotificationId = NOTIF_BASE_ID;
+ protected final NotificationManager mNotificationManager;
+ protected final UserContextProvider mUserContextTracker;
+ protected int mNotificationId = NOTIF_BASE_ID;
private RecordingServiceStrings mStrings;
@Inject
@@ -185,7 +187,10 @@
return Service.START_NOT_STICKY;
}
break;
-
+ case ACTION_SHOW_START_NOTIF:
+ createRecordingNotification();
+ mUiEventLogger.log(Events.ScreenRecordEvent.SCREEN_RECORD_START);
+ break;
case ACTION_STOP_NOTIF:
case ACTION_STOP:
// only difference for actions is the log event
@@ -232,6 +237,7 @@
super.onCreate();
}
+ @Nullable
@VisibleForTesting
protected ScreenMediaRecorder getRecorder() {
return mRecorder;
@@ -337,8 +343,9 @@
}
@VisibleForTesting
- protected Notification createSaveNotification(ScreenMediaRecorder.SavedRecording recording) {
- Uri uri = recording.getUri();
+ protected Notification createSaveNotification(
+ @Nullable ScreenMediaRecorder.SavedRecording recording) {
+ Uri uri = recording != null ? recording.getUri() : null;
Intent viewIntent = new Intent(Intent.ACTION_VIEW)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION)
.setDataAndType(uri, "video/mp4");
@@ -349,7 +356,7 @@
PendingIntent.getService(
this,
REQUEST_CODE,
- getShareIntent(this, uri.toString()),
+ getShareIntent(this, uri != null ? uri.toString() : null),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE))
.build();
@@ -371,9 +378,10 @@
.addExtras(extras);
// Add thumbnail if available
- if (recording.getThumbnail() != null) {
+ Icon thumbnail = recording != null ? recording.getThumbnail() : null;
+ if (thumbnail != null) {
Notification.BigPictureStyle pictureStyle = new Notification.BigPictureStyle()
- .bigPicture(recording.getThumbnail())
+ .bigPicture(thumbnail)
.showBigPictureWhenCollapsed(true);
builder.setStyle(pictureStyle);
}
@@ -408,27 +416,29 @@
}
Log.d(getTag(), "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
- if (getRecorder() != null) {
- try {
+ try {
+ if (getRecorder() != null) {
getRecorder().end();
- saveRecording(userId);
- } catch (RuntimeException exception) {
+ }
+ saveRecording(userId);
+ } catch (RuntimeException exception) {
+ if (getRecorder() != null) {
// RuntimeException could happen if the recording stopped immediately after starting
// let's release the recorder and delete all temporary files in this case
getRecorder().release();
- showErrorToast(R.string.screenrecord_start_error);
- Log.e(getTag(), "stopRecording called, but there was an error when ending"
- + "recording");
- exception.printStackTrace();
- createErrorNotification();
- } catch (Throwable throwable) {
+ }
+ showErrorToast(R.string.screenrecord_start_error);
+ Log.e(getTag(), "stopRecording called, but there was an error when ending"
+ + "recording");
+ exception.printStackTrace();
+ createErrorNotification();
+ } catch (Throwable throwable) {
+ if (getRecorder() != null) {
// Something unexpected happen, SystemUI will crash but let's delete
// the temporary files anyway
getRecorder().release();
- throw new RuntimeException(throwable);
}
- } else {
- Log.e(getTag(), "stopRecording called, but recorder was null");
+ throw new RuntimeException(throwable);
}
updateState(false);
stopForeground(STOP_FOREGROUND_DETACH);
@@ -443,7 +453,8 @@
mLongExecutor.execute(() -> {
try {
Log.d(getTag(), "saving recording");
- Notification notification = createSaveNotification(getRecorder().save());
+ Notification notification = createSaveNotification(
+ getRecorder() != null ? getRecorder().save() : null);
postGroupNotification(currentUser);
mNotificationManager.notifyAsUser(null, mNotificationId, notification,
currentUser);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
rename to packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
index b98093e..9f1447b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialogDelegate.java
@@ -49,52 +49,69 @@
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
import java.util.Arrays;
import java.util.List;
/**
* Dialog to select screen recording options
*/
-public class ScreenRecordDialog extends SystemUIDialog {
+public class ScreenRecordDialogDelegate implements SystemUIDialog.Delegate {
private static final List<ScreenRecordingAudioSource> MODES = Arrays.asList(INTERNAL, MIC,
MIC_AND_INTERNAL);
private static final long DELAY_MS = 3000;
private static final long INTERVAL_MS = 1000;
- private final RecordingController mController;
+ private final SystemUIDialog.Factory mSystemUIDialogFactory;
private final UserContextProvider mUserContextProvider;
- @Nullable
+ private final RecordingController mController;
private final Runnable mOnStartRecordingClicked;
private Switch mTapsSwitch;
private Switch mAudioSwitch;
private Spinner mOptions;
- public ScreenRecordDialog(Context context,
- RecordingController controller,
- UserContextProvider userContextProvider,
- @Nullable Runnable onStartRecordingClicked) {
- super(context);
- mController = controller;
+ @AssistedFactory
+ public interface Factory {
+ ScreenRecordDialogDelegate create(
+ RecordingController recordingController,
+ @Nullable Runnable onStartRecordingClicked
+ );
+ }
+
+ @AssistedInject
+ public ScreenRecordDialogDelegate(
+ SystemUIDialog.Factory systemUIDialogFactory,
+ UserContextProvider userContextProvider,
+ @Assisted RecordingController controller,
+ @Assisted @Nullable Runnable onStartRecordingClicked) {
+ mSystemUIDialogFactory = systemUIDialogFactory;
mUserContextProvider = userContextProvider;
+ mController = controller;
mOnStartRecordingClicked = onStartRecordingClicked;
}
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
+ public SystemUIDialog createDialog() {
+ return mSystemUIDialogFactory.create(this);
+ }
- Window window = getWindow();
+ @Override
+ public void onCreate(SystemUIDialog dialog, Bundle savedInstanceState) {
+ Window window = dialog.getWindow();
window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setGravity(Gravity.CENTER);
- setTitle(R.string.screenrecord_title);
+ dialog.setTitle(R.string.screenrecord_title);
- setContentView(R.layout.screen_record_dialog);
+ dialog.setContentView(R.layout.screen_record_dialog);
- TextView cancelBtn = findViewById(R.id.button_cancel);
- cancelBtn.setOnClickListener(v -> dismiss());
- TextView startBtn = findViewById(R.id.button_start);
+ TextView cancelBtn = dialog.findViewById(R.id.button_cancel);
+ cancelBtn.setOnClickListener(v -> dialog.dismiss());
+ TextView startBtn = dialog.findViewById(R.id.button_start);
startBtn.setOnClickListener(v -> {
if (mOnStartRecordingClicked != null) {
// Note that it is important to run this callback before dismissing, so that the
@@ -104,13 +121,13 @@
// Start full-screen recording
requestScreenCapture(/* captureTarget= */ null);
- dismiss();
+ dialog.dismiss();
});
- mAudioSwitch = findViewById(R.id.screenrecord_audio_switch);
- mTapsSwitch = findViewById(R.id.screenrecord_taps_switch);
- mOptions = findViewById(R.id.screen_recording_options);
- ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(),
+ mAudioSwitch = dialog.findViewById(R.id.screenrecord_audio_switch);
+ mTapsSwitch = dialog.findViewById(R.id.screenrecord_taps_switch);
+ mOptions = dialog.findViewById(R.id.screen_recording_options);
+ ArrayAdapter a = new ScreenRecordingAdapter(dialog.getContext().getApplicationContext(),
android.R.layout.simple_spinner_dropdown_item,
MODES);
a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index 3eb26f4..ba775cd3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -46,17 +46,20 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
/** Dialog to select screen recording options */
-class ScreenRecordPermissionDialogDelegate(
- private val hostUserHandle: UserHandle,
- private val hostUid: Int,
- private val controller: RecordingController,
+class ScreenRecordPermissionDialogDelegate @AssistedInject constructor(
+ @Assisted private val hostUserHandle: UserHandle,
+ @Assisted private val hostUid: Int,
+ @Assisted private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
- private val onStartRecordingClicked: Runnable?,
+ @Assisted private val onStartRecordingClicked: Runnable?,
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
- private val systemUIDialogFactory: SystemUIDialog.Factory
+ private val systemUIDialogFactory: SystemUIDialog.Factory,
) :
BaseMediaProjectionPermissionDialogDelegate<SystemUIDialog>(
createOptionList(),
@@ -65,8 +68,19 @@
mediaProjectionMetricsLogger,
R.drawable.ic_screenrecord,
R.color.screenrecord_icon_color
- ),
- SystemUIDialog.Delegate {
+ ), SystemUIDialog.Delegate {
+
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ recordingController: RecordingController,
+ hostUserHandle: UserHandle,
+ hostUid: Int,
+ onStartRecordingClicked: Runnable?
+ ): ScreenRecordPermissionDialogDelegate
+ }
+
private lateinit var tapsSwitch: Switch
private lateinit var tapsSwitchContainer: ViewGroup
private lateinit var tapsView: View
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
new file mode 100644
index 0000000..d8c3850
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.util.Log
+import android.view.Display
+import android.view.LayoutInflater
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedCallback
+import android.window.OnBackInvokedDispatcher
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
+
+/**
+ * Legacy implementation of screenshot view methods. Just proxies the calls down into the original
+ * ScreenshotView.
+ */
+class LegacyScreenshotViewProxy(context: Context) : ScreenshotViewProxy {
+ override val view: ScreenshotView =
+ LayoutInflater.from(context).inflate(R.layout.screenshot, null) as ScreenshotView
+ override val screenshotPreview: View
+
+ override var defaultDisplay: Int = Display.DEFAULT_DISPLAY
+ set(value) {
+ view.setDefaultDisplay(value)
+ }
+ override var defaultTimeoutMillis: Long = 6000
+ set(value) {
+ view.setDefaultTimeoutMillis(value)
+ }
+ override var onBackInvokedCallback: OnBackInvokedCallback = OnBackInvokedCallback {
+ Log.wtf(TAG, "OnBackInvoked called before being set!")
+ }
+ override var onKeyListener: View.OnKeyListener? = null
+ set(value) {
+ view.setOnKeyListener(value)
+ }
+ override var flags: FeatureFlags? = null
+ set(value) {
+ view.setFlags(value)
+ }
+ override var packageName: String = ""
+ set(value) {
+ view.setPackageName(value)
+ }
+ override var logger: UiEventLogger? = null
+ set(value) {
+ view.setUiEventLogger(value)
+ }
+ override var callbacks: ScreenshotView.ScreenshotViewCallback? = null
+ set(value) {
+ view.setCallbacks(value)
+ }
+ override var screenshot: ScreenshotData? = null
+ set(value) {
+ view.setScreenshot(value)
+ }
+
+ override val isAttachedToWindow
+ get() = view.isAttachedToWindow
+ override val isDismissing
+ get() = view.isDismissing
+ override val isPendingSharedTransition
+ get() = view.isPendingSharedTransition
+
+ init {
+
+ view.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(view: View) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Registering Predictive Back callback")
+ }
+ view
+ .findOnBackInvokedDispatcher()
+ ?.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ onBackInvokedCallback
+ )
+ }
+
+ override fun onViewDetachedFromWindow(view: View) {
+ if (LogConfig.DEBUG_INPUT) {
+ Log.d(TAG, "Unregistering Predictive Back callback")
+ }
+ view
+ .findOnBackInvokedDispatcher()
+ ?.unregisterOnBackInvokedCallback(onBackInvokedCallback)
+ }
+ }
+ )
+ if (LogConfig.DEBUG_WINDOW) {
+ Log.d(TAG, "adding OnComputeInternalInsetsListener")
+ }
+ view.viewTreeObserver.addOnComputeInternalInsetsListener(view)
+ screenshotPreview = view.screenshotPreview
+ }
+
+ override fun reset() = view.reset()
+ override fun updateInsets(insets: WindowInsets) = view.updateInsets(insets)
+ override fun updateOrientation(insets: WindowInsets) = view.updateOrientation(insets)
+
+ override fun badgeScreenshot(userBadgedIcon: Drawable) = view.badgeScreenshot(userBadgedIcon)
+
+ override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator =
+ view.createScreenshotDropInAnimation(screenRect, showFlash)
+
+ override fun addQuickShareChip(quickShareAction: Notification.Action) =
+ view.addQuickShareChip(quickShareAction)
+
+ override fun setChipIntents(imageData: ScreenshotController.SavedImageData) =
+ view.setChipIntents(imageData)
+
+ override fun animateDismissal() = view.animateDismissal()
+
+ override fun showScrollChip(packageName: String, onClick: Runnable) =
+ view.showScrollChip(packageName, onClick)
+
+ override fun hideScrollChip() = view.hideScrollChip()
+
+ override fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ ) =
+ view.prepareScrollingTransition(
+ response,
+ screenBitmap,
+ newScreenshot,
+ screenshotTakenInPortrait
+ )
+
+ override fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ ) = view.startLongScreenshotTransition(transitionDestination, onTransitionEnd, longScreenshot)
+
+ override fun restoreNonScrollingUi() = view.restoreNonScrollingUi()
+
+ override fun stopInputListening() = view.stopInputListening()
+
+ override fun requestFocus() {
+ view.requestFocus()
+ }
+
+ override fun announceForAccessibility(string: String) = view.announceForAccessibility(string)
+
+ override fun getViewTreeObserver(): ViewTreeObserver = view.viewTreeObserver
+
+ override fun post(runnable: Runnable) {
+ view.post(runnable)
+ }
+
+ class Factory : ScreenshotViewProxy.Factory {
+ override fun getProxy(context: Context): ScreenshotViewProxy {
+ return LegacyScreenshotViewProxy(context)
+ }
+ }
+
+ companion object {
+ private const val TAG = "LegacyScreenshotViewProxy"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index ee3e7ba..1ca9b98 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -65,7 +65,6 @@
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.ScrollCaptureResponse;
@@ -77,8 +76,6 @@
import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
-import android.window.OnBackInvokedCallback;
-import android.window.OnBackInvokedDispatcher;
import android.window.WindowContext;
import com.android.internal.app.ChooserActivity;
@@ -165,7 +162,7 @@
/**
* Structure returned by the SaveImageInBackgroundTask
*/
- static class SavedImageData {
+ public static class SavedImageData {
public Uri uri;
public List<Notification.Action> smartActions;
public Notification.Action quickShareAction;
@@ -237,6 +234,7 @@
private final WindowContext mContext;
private final FeatureFlags mFlags;
+ private final ScreenshotViewProxy mViewProxy;
private final ScreenshotNotificationsController mNotificationsController;
private final ScreenshotSmartActions mScreenshotSmartActions;
private final UiEventLogger mUiEventLogger;
@@ -265,14 +263,6 @@
private final UserManager mUserManager;
private final AssistContentRequester mAssistContentRequester;
- private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Predictive Back callback dispatched");
- }
- respondToKeyDismissal();
- };
-
- private ScreenshotView mScreenshotView;
private final MessageContainerController mMessageContainerController;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -305,6 +295,7 @@
ScreenshotController(
Context context,
FeatureFlags flags,
+ ScreenshotViewProxy.Factory viewProxyFactory,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotNotificationsController.Factory screenshotNotificationsControllerFactory,
ScrollCaptureClient scrollCaptureClient,
@@ -360,6 +351,8 @@
mMessageContainerController = messageContainerController;
mAssistContentRequester = assistContentRequester;
+ mViewProxy = viewProxyFactory.getProxy(mContext);
+
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
// Setup the window that we are going to use
@@ -461,7 +454,7 @@
// The window is focusable by default
setWindowFocusable(true);
- mScreenshotView.requestFocus();
+ mViewProxy.requestFocus();
enqueueScrollCaptureRequest(screenshot.getUserHandle());
@@ -485,10 +478,10 @@
mMessageContainerController.onScreenshotTaken(screenshot);
});
- mScreenshotView.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
+ mViewProxy.badgeScreenshot(mContext.getPackageManager().getUserBadgedIcon(
mContext.getDrawable(R.drawable.overlay_badge_background),
screenshot.getUserHandle()));
- mScreenshotView.setScreenshot(screenshot);
+ mViewProxy.setScreenshot(screenshot);
// ignore system bar insets for the purpose of window layout
mWindow.getDecorView().setOnApplyWindowInsetsListener(
@@ -503,31 +496,31 @@
void prepareViewForNewScreenshot(ScreenshotData screenshot, String oldPackageName) {
withWindowAttached(() -> {
if (mUserManager.isManagedProfile(screenshot.getUserHandle().getIdentifier())) {
- mScreenshotView.announceForAccessibility(mContext.getResources().getString(
+ mViewProxy.announceForAccessibility(mContext.getResources().getString(
R.string.screenshot_saving_work_profile_title));
} else {
- mScreenshotView.announceForAccessibility(
+ mViewProxy.announceForAccessibility(
mContext.getResources().getString(R.string.screenshot_saving_title));
}
});
- mScreenshotView.reset();
+ mViewProxy.reset();
- if (mScreenshotView.isAttachedToWindow()) {
+ if (mViewProxy.isAttachedToWindow()) {
// if we didn't already dismiss for another reason
- if (!mScreenshotView.isDismissing()) {
+ if (!mViewProxy.isDismissing()) {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED, 0,
oldPackageName);
}
if (DEBUG_WINDOW) {
Log.d(TAG, "saveScreenshot: screenshotView is already attached, resetting. "
- + "(dismissing=" + mScreenshotView.isDismissing() + ")");
+ + "(dismissing=" + mViewProxy.isDismissing() + ")");
}
}
- mScreenshotView.setPackageName(mPackageName);
+ mViewProxy.setPackageName(mPackageName);
- mScreenshotView.updateOrientation(
+ mViewProxy.updateOrientation(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
}
@@ -539,7 +532,7 @@
Log.d(TAG, "dismissScreenshot");
}
// If we're already animating out, don't restart the animation
- if (mScreenshotView.isDismissing()) {
+ if (mViewProxy.isDismissing()) {
if (DEBUG_DISMISS) {
Log.v(TAG, "Already dismissing, ignoring duplicate command");
}
@@ -547,11 +540,11 @@
}
mUiEventLogger.log(event, 0, mPackageName);
mScreenshotHandler.cancelTimeout();
- mScreenshotView.animateDismissal();
+ mViewProxy.animateDismissal();
}
boolean isPendingSharedTransition() {
- return mScreenshotView.isPendingSharedTransition();
+ return mViewProxy.isPendingSharedTransition();
}
// Any cleanup needed when the service is being destroyed.
@@ -576,7 +569,7 @@
private void releaseMediaPlayer() {
if (mScreenshotSoundController == null) return;
- mScreenshotSoundController.releaseScreenshotSound();
+ mScreenshotSoundController.releaseScreenshotSoundAsync();
}
private void respondToKeyDismissal() {
@@ -591,31 +584,15 @@
Log.d(TAG, "reloadAssets()");
}
- // Inflate the screenshot layout
- mScreenshotView = (ScreenshotView)
- LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
- mMessageContainerController.setView(mScreenshotView);
- mScreenshotView.addOnAttachStateChangeListener(
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(@NonNull View v) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Registering Predictive Back callback");
- }
- mScreenshotView.findOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback);
- }
-
- @Override
- public void onViewDetachedFromWindow(@NonNull View v) {
- if (DEBUG_INPUT) {
- Log.d(TAG, "Unregistering Predictive Back callback");
- }
- mScreenshotView.findOnBackInvokedDispatcher()
- .unregisterOnBackInvokedCallback(mOnBackInvokedCallback);
- }
- });
- mScreenshotView.init(mUiEventLogger, new ScreenshotView.ScreenshotViewCallback() {
+ mMessageContainerController.setView(mViewProxy.getView());
+ mViewProxy.setLogger(mUiEventLogger);
+ mViewProxy.setOnBackInvokedCallback(() -> {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched");
+ }
+ respondToKeyDismissal();
+ });
+ mViewProxy.setCallbacks(new ScreenshotView.ScreenshotViewCallback() {
@Override
public void onUserInteraction() {
if (DEBUG_INPUT) {
@@ -640,11 +617,12 @@
// TODO(159460485): Remove this when focus is handled properly in the system
setWindowFocusable(false);
}
- }, mFlags);
- mScreenshotView.setDefaultDisplay(mDisplayId);
- mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
+ });
+ mViewProxy.setFlags(mFlags);
+ mViewProxy.setDefaultDisplay(mDisplayId);
+ mViewProxy.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
- mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
+ mViewProxy.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: " + keyCode);
@@ -656,25 +634,21 @@
});
if (DEBUG_WINDOW) {
- Log.d(TAG, "adding OnComputeInternalInsetsListener");
+ Log.d(TAG, "setContentView: " + mViewProxy.getView());
}
- mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(mScreenshotView);
- if (DEBUG_WINDOW) {
- Log.d(TAG, "setContentView: " + mScreenshotView);
- }
- setContentView(mScreenshotView);
+ setContentView(mViewProxy.getView());
}
private void prepareAnimation(Rect screenRect, boolean showFlash,
Runnable onAnimationComplete) {
- mScreenshotView.getViewTreeObserver().addOnPreDrawListener(
+ mViewProxy.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (DEBUG_WINDOW) {
Log.d(TAG, "onPreDraw: startAnimation");
}
- mScreenshotView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mViewProxy.getViewTreeObserver().removeOnPreDrawListener(this);
startAnimation(screenRect, showFlash, onAnimationComplete);
return true;
}
@@ -694,13 +668,13 @@
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
// Hide the scroll chip until we know it's available in this
// orientation
- mScreenshotView.hideScrollChip();
+ mViewProxy.hideScrollChip();
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(() -> {
requestScrollCapture(owner);
}, 150);
- mScreenshotView.updateInsets(
+ mViewProxy.updateInsets(
mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// Screenshot animation calculations won't be valid anymore,
// so just end
@@ -759,16 +733,16 @@
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
+ mViewProxy.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId,
new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels));
- mScreenshotView.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
+ mViewProxy.prepareScrollingTransition(response, mScreenBitmap, newScreenshot,
mScreenshotTakenInPortrait);
// delay starting scroll capture to make sure the scrim is up before the app moves
- mScreenshotView.post(() -> runBatchScrollCapture(response, owner));
+ mViewProxy.post(() -> runBatchScrollCapture(response, owner));
});
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestScrollCapture failed", e);
@@ -794,19 +768,19 @@
return;
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "Exception", e);
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
if (longScreenshot.getHeight() == 0) {
- mScreenshotView.restoreNonScrollingUi();
+ mViewProxy.restoreNonScrollingUi();
return;
}
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
mLongScreenshotHolder.setTransitionDestinationCallback(
(transitionDestination, onTransitionEnd) -> {
- mScreenshotView.startLongScreenshotTransition(
+ mViewProxy.startLongScreenshotTransition(
transitionDestination, onTransitionEnd,
longScreenshot);
// TODO: Do this via ActionIntentExecutor instead.
@@ -882,16 +856,14 @@
}
mWindowManager.removeViewImmediate(decorView);
}
- // Ensure that we remove the input monitor
- if (mScreenshotView != null) {
- mScreenshotView.stopInputListening();
- }
+
+ mViewProxy.stopInputListening();
}
private void playCameraSoundIfNeeded() {
if (mScreenshotSoundController == null) return;
// the controller is not-null only on the default display controller
- mScreenshotSoundController.playCameraSound();
+ mScreenshotSoundController.playScreenshotSoundAsync();
}
/**
@@ -932,7 +904,7 @@
}
mScreenshotAnimation =
- mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash);
+ mViewProxy.createScreenshotDropInAnimation(screenRect, showFlash);
if (onAnimationComplete != null) {
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
@@ -975,7 +947,7 @@
};
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
ActivityOptions.startSharedElementAnimation(mWindow, callbacks, null,
- Pair.create(mScreenshotView.getScreenshotPreview(),
+ Pair.create(mViewProxy.getScreenshotPreview(),
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
return transition;
@@ -999,7 +971,7 @@
mCurrentRequestCallback.onFinish();
mCurrentRequestCallback = null;
}
- mScreenshotView.reset();
+ mViewProxy.reset();
removeWindow();
mScreenshotHandler.cancelTimeout();
}
@@ -1067,7 +1039,7 @@
}
private void doPostAnimation(ScreenshotController.SavedImageData imageData) {
- mScreenshotView.setChipIntents(imageData);
+ mViewProxy.setChipIntents(imageData);
}
/**
@@ -1084,11 +1056,11 @@
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
} else {
- mScreenshotView.addQuickShareChip(quickShareData.quickShareAction);
+ mViewProxy.addQuickShareChip(quickShareData.quickShareAction);
}
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
index 2c0bdde..d3a7fc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt
@@ -21,22 +21,34 @@
import com.android.app.tracing.coroutines.async
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.google.errorprone.annotations.CanIgnoreReturnValue
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
/** Controls sound reproduction after a screenshot is taken. */
interface ScreenshotSoundController {
/** Reproduces the camera sound. */
- @CanIgnoreReturnValue fun playCameraSound(): Deferred<Unit>
+ suspend fun playScreenshotSound()
- /** Releases the sound. [playCameraSound] behaviour is undefined after this has been called. */
- @CanIgnoreReturnValue fun releaseScreenshotSound(): Deferred<Unit>
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ */
+ suspend fun releaseScreenshotSound()
+
+ /** Reproduces the camera sound. Used for compatibility with Java code. */
+ fun playScreenshotSoundAsync()
+
+ /**
+ * Releases the sound. [playScreenshotSound] behaviour is undefined after this has been called.
+ * Used for compatibility with Java code.
+ */
+ fun releaseScreenshotSoundAsync()
}
class ScreenshotSoundControllerImpl
@@ -47,8 +59,8 @@
@Background private val bgDispatcher: CoroutineDispatcher
) : ScreenshotSoundController {
- val player: Deferred<MediaPlayer?> =
- coroutineScope.async("loadCameraSound", bgDispatcher) {
+ private val player: Deferred<MediaPlayer?> =
+ coroutineScope.async("loadScreenshotSound", bgDispatcher) {
try {
soundProvider.getScreenshotSound()
} catch (e: IllegalStateException) {
@@ -57,8 +69,8 @@
}
}
- override fun playCameraSound(): Deferred<Unit> {
- return coroutineScope.async("playCameraSound", bgDispatcher) {
+ override suspend fun playScreenshotSound() {
+ withContext(bgDispatcher) {
try {
player.await()?.start()
} catch (e: IllegalStateException) {
@@ -68,8 +80,8 @@
}
}
- override fun releaseScreenshotSound(): Deferred<Unit> {
- return coroutineScope.async("releaseScreenshotSound", bgDispatcher) {
+ override suspend fun releaseScreenshotSound() {
+ withContext(bgDispatcher) {
try {
withTimeout(1.seconds) { player.await()?.release() }
} catch (e: TimeoutCancellationException) {
@@ -79,6 +91,14 @@
}
}
+ override fun playScreenshotSoundAsync() {
+ coroutineScope.launch { playScreenshotSound() }
+ }
+
+ override fun releaseScreenshotSoundAsync() {
+ coroutineScope.launch { releaseScreenshotSound() }
+ }
+
private companion object {
const val TAG = "ScreenshotSoundControllerImpl"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index be30a15..8a8766d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -102,7 +102,7 @@
public class ScreenshotView extends FrameLayout implements
ViewTreeObserver.OnComputeInternalInsetsListener {
- interface ScreenshotViewCallback {
+ public interface ScreenshotViewCallback {
void onUserInteraction();
void onAction(Intent intent, UserHandle owner, boolean overrideTransition);
@@ -426,15 +426,15 @@
return mScreenshotPreview;
}
- /**
- * Set up the logger and callback on dismissal.
- *
- * Note: must be called before any other (non-constructor) method or null pointer exceptions
- * may occur.
- */
- void init(UiEventLogger uiEventLogger, ScreenshotViewCallback callbacks, FeatureFlags flags) {
+ void setUiEventLogger(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
+ }
+
+ void setCallbacks(ScreenshotViewCallback callbacks) {
mCallbacks = callbacks;
+ }
+
+ void setFlags(FeatureFlags flags) {
mFlags = flags;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
new file mode 100644
index 0000000..381404a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.animation.Animator
+import android.app.Notification
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.ScrollCaptureResponse
+import android.view.View
+import android.view.View.OnKeyListener
+import android.view.ViewGroup
+import android.view.ViewTreeObserver
+import android.view.WindowInsets
+import android.window.OnBackInvokedCallback
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.flags.FeatureFlags
+
+/** Abstraction of the surface between ScreenshotController and ScreenshotView */
+interface ScreenshotViewProxy {
+ val view: ViewGroup
+ val screenshotPreview: View
+
+ var defaultDisplay: Int
+ var defaultTimeoutMillis: Long
+ var onBackInvokedCallback: OnBackInvokedCallback
+ var onKeyListener: OnKeyListener?
+ var flags: FeatureFlags?
+ var packageName: String
+ var logger: UiEventLogger?
+ var callbacks: ScreenshotView.ScreenshotViewCallback?
+ var screenshot: ScreenshotData?
+
+ val isAttachedToWindow: Boolean
+ val isDismissing: Boolean
+ val isPendingSharedTransition: Boolean
+
+ fun reset()
+ fun updateInsets(insets: WindowInsets)
+ fun updateOrientation(insets: WindowInsets)
+ fun badgeScreenshot(userBadgedIcon: Drawable)
+ fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator
+ fun addQuickShareChip(quickShareAction: Notification.Action)
+ fun setChipIntents(imageData: ScreenshotController.SavedImageData)
+ fun animateDismissal()
+
+ fun showScrollChip(packageName: String, onClick: Runnable)
+ fun hideScrollChip()
+ fun prepareScrollingTransition(
+ response: ScrollCaptureResponse,
+ screenBitmap: Bitmap,
+ newScreenshot: Bitmap,
+ screenshotTakenInPortrait: Boolean
+ )
+ fun startLongScreenshotTransition(
+ transitionDestination: Rect,
+ onTransitionEnd: Runnable,
+ longScreenshot: ScrollCaptureController.LongScreenshot
+ )
+ fun restoreNonScrollingUi()
+
+ fun stopInputListening()
+ fun requestFocus()
+ fun announceForAccessibility(string: String)
+ fun getViewTreeObserver(): ViewTreeObserver
+ fun post(runnable: Runnable)
+
+ interface Factory {
+ fun getProxy(context: Context): ScreenshotViewProxy
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index bb34ede..8a2678c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -75,7 +75,7 @@
private String mWindowOwner;
private volatile boolean mCancelled;
- static class LongScreenshot {
+ public static class LongScreenshot {
private final ImageTileSet mImageTileSet;
private final Session mSession;
// TODO: Add UserHandle so LongScreenshots can adhere to work profile screenshot policy
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
index 3797b8b..a00c81d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/dagger/ScreenshotModule.java
@@ -20,6 +20,7 @@
import com.android.systemui.screenshot.ImageCapture;
import com.android.systemui.screenshot.ImageCaptureImpl;
+import com.android.systemui.screenshot.LegacyScreenshotViewProxy;
import com.android.systemui.screenshot.RequestProcessor;
import com.android.systemui.screenshot.ScreenshotPolicy;
import com.android.systemui.screenshot.ScreenshotPolicyImpl;
@@ -29,12 +30,14 @@
import com.android.systemui.screenshot.ScreenshotSoundControllerImpl;
import com.android.systemui.screenshot.ScreenshotSoundProvider;
import com.android.systemui.screenshot.ScreenshotSoundProviderImpl;
+import com.android.systemui.screenshot.ScreenshotViewProxy;
import com.android.systemui.screenshot.TakeScreenshotService;
import com.android.systemui.screenshot.appclips.AppClipsScreenshotHelperService;
import com.android.systemui.screenshot.appclips.AppClipsService;
import dagger.Binds;
import dagger.Module;
+import dagger.Provides;
import dagger.multibindings.ClassKey;
import dagger.multibindings.IntoMap;
@@ -81,4 +84,9 @@
@Binds
abstract ScreenshotSoundController bindScreenshotSoundController(
ScreenshotSoundControllerImpl screenshotSoundProviderImpl);
+
+ @Provides
+ static ScreenshotViewProxy.Factory providesScreenshotViewProxyFactory() {
+ return new LegacyScreenshotViewProxy.Factory();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index d0585d3..20bd7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -64,8 +64,6 @@
private String mScrimName;
private int mTintColor;
private boolean mBlendWithMainColor = true;
- private Runnable mChangeRunnable;
- private Executor mChangeRunnableExecutor;
private Executor mExecutor;
private Looper mExecutorLooper;
@Nullable
@@ -270,9 +268,6 @@
mDrawable.invalidateSelf();
}
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
public int getTint() {
@@ -300,9 +295,6 @@
mViewAlpha = alpha;
mDrawable.setAlpha((int) (255 * alpha));
- if (mChangeRunnable != null) {
- mChangeRunnableExecutor.execute(mChangeRunnable);
- }
}
});
}
@@ -311,14 +303,6 @@
return mViewAlpha;
}
- /**
- * Sets a callback that is invoked whenever the alpha, color, or tint change.
- */
- public void setChangeRunnable(Runnable changeRunnable, Executor changeRunnableExecutor) {
- mChangeRunnable = changeRunnable;
- mChangeRunnableExecutor = changeRunnableExecutor;
- }
-
@Override
protected boolean canReceivePointerEvents() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index cf7c3e0..a1644b2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -351,7 +351,7 @@
private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
- private final QuickSettingsController mQsController;
+ private final QuickSettingsControllerImpl mQsController;
private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
@@ -726,7 +726,7 @@
TapAgainViewController tapAgainViewController,
NavigationModeController navigationModeController,
NavigationBarController navigationBarController,
- QuickSettingsController quickSettingsController,
+ QuickSettingsControllerImpl quickSettingsController,
FragmentService fragmentService,
IStatusBarService statusBarService,
ContentResolver contentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
new file mode 100644
index 0000000..c96a339
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+interface QuickSettingsController {
+ /** Returns whether or not QuickSettings is expanded. */
+ val expanded: Boolean
+
+ /** Returns whether or not QuickSettings is being customized. */
+ val isCustomizing: Boolean
+
+ /** Returns Whether we should intercept a gesture to open Quick Settings. */
+ @Deprecated("specific to legacy touch handling")
+ fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean
+
+ /** Closes the Qs customizer. */
+ fun closeQsCustomizer()
+
+ /**
+ * This method closes QS but in split shade it should be used only in special cases: to make
+ * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing from
+ * split shade
+ */
+ @Deprecated("specific to legacy split shade") fun closeQs()
+
+ /** Calculate top padding for notifications */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float,
+ ): Float
+
+ /** Calculate height of QS panel */
+ @Deprecated("specific to legacy DebugDrawable")
+ fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
rename to packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index a5c0553..19d98a0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -118,7 +118,7 @@
* TODO (b/264460656) make this dumpable
*/
@SysUISingleton
-public class QuickSettingsController implements Dumpable {
+public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
public static final String TAG = "QuickSettingsController";
public static final int SHADE_BACK_ANIM_SCALE_MULTIPLIER = 100;
@@ -252,7 +252,7 @@
/**
* The window width currently in effect -- used together with
- * {@link QuickSettingsController#mCachedGestureInsets} to decide whether a back gesture should
+ * {@link QuickSettingsControllerImpl#mCachedGestureInsets} to decide whether a back gesture should
* receive a horizontal swipe inwards from the left/right vertical edge of the screen.
* We cache this on ACTION_DOWN, and query it during both ACTION_DOWN and ACTION_MOVE events.
*/
@@ -304,7 +304,7 @@
private final QS.ScrollListener mQsScrollListener = this::onScroll;
@Inject
- public QuickSettingsController(
+ public QuickSettingsControllerImpl(
Lazy<NotificationPanelViewController> panelViewControllerLazy,
NotificationPanelView panelView,
QsFrameTranslateController qsFrameTranslateController,
@@ -399,23 +399,23 @@
mQs = qs;
}
- public void setExpansionHeightListener(ExpansionHeightListener listener) {
+ void setExpansionHeightListener(ExpansionHeightListener listener) {
mExpansionHeightListener = listener;
}
- public void setQsStateUpdateListener(QsStateUpdateListener listener) {
+ void setQsStateUpdateListener(QsStateUpdateListener listener) {
mQsStateUpdateListener = listener;
}
- public void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
+ void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
mApplyClippingImmediatelyListener = listener;
}
- public void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
+ void setFlingQsWithoutClickListener(FlingQsWithoutClickListener listener) {
mFlingQsWithoutClickListener = listener;
}
- public void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
+ void setExpansionHeightSetToMaxListener(ExpansionHeightSetToMaxListener callback) {
mExpansionHeightSetToMaxListener = callback;
}
@@ -507,15 +507,11 @@
&& mPanelView.getRootWindowInsets().isVisible(ime());
}
- public boolean isExpansionEnabled() {
+ boolean isExpansionEnabled() {
return mExpansionEnabledPolicy && mExpansionEnabledAmbient
&& !isRemoteInputActiveWithKeyboardUp();
}
- public float getTransitioningToFullShadeProgress() {
- return mTransitioningToFullShadeProgress;
- }
-
/** */
@VisibleForTesting
boolean isExpandImmediate() {
@@ -536,7 +532,7 @@
* Computes (and caches) the gesture insets for the current window. Intended to be called
* on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
*/
- public void updateGestureInsetsCache() {
+ void updateGestureInsetsCache() {
WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
@@ -548,7 +544,7 @@
* Returns whether x coordinate lies in the vertical edges of the screen
* (the only place where a back gesture can be initiated).
*/
- public boolean shouldBackBypassQuickSettings(float touchX) {
+ boolean shouldBackBypassQuickSettings(float touchX) {
return (touchX < mCachedGestureInsets.left)
|| (touchX > mCachedWindowWidth - mCachedGestureInsets.right);
}
@@ -592,6 +588,7 @@
return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
}
+ @Override
public boolean getExpanded() {
return mShadeRepository.getLegacyIsQsExpanded().getValue();
}
@@ -601,7 +598,7 @@
return mShadeRepository.getLegacyQsTracking().getValue();
}
- public boolean getFullyExpanded() {
+ boolean getFullyExpanded() {
return mFullyExpanded;
}
@@ -623,27 +620,28 @@
return mQs != null;
}
+ @Override
public boolean isCustomizing() {
return isQsFragmentCreated() && mQs.isCustomizing();
}
- public float getExpansionHeight() {
+ float getExpansionHeight() {
return mExpansionHeight;
}
- public boolean getExpandedWhenExpandingStarted() {
+ boolean getExpandedWhenExpandingStarted() {
return mExpandedWhenExpandingStarted;
}
- public int getMinExpansionHeight() {
+ int getMinExpansionHeight() {
return mMinExpansionHeight;
}
- public boolean isFullyExpandedAndTouchesDisallowed() {
+ boolean isFullyExpandedAndTouchesDisallowed() {
return isQsFragmentCreated() && getFullyExpanded() && disallowTouches();
}
- public int getMaxExpansionHeight() {
+ int getMaxExpansionHeight() {
return mMaxExpansionHeight;
}
@@ -654,13 +652,14 @@
return !mTouchAboveFalsingThreshold;
}
- public int getFalsingThreshold() {
+ int getFalsingThreshold() {
return mFalsingThreshold;
}
/**
* Returns Whether we should intercept a gesture to open Quick Settings.
*/
+ @Override
public boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
boolean keyguardShowing = mBarState == KEYGUARD;
if (!isExpansionEnabled() || mCollapsedOnDown || (keyguardShowing
@@ -717,7 +716,7 @@
* @param downY the y location where the touch started
* Returns true if the panel could be collapsed because it stared on QQS
*/
- public boolean canPanelCollapseOnQQS(float downX, float downY) {
+ boolean canPanelCollapseOnQQS(float downX, float downY) {
if (mCollapsedOnDown || mBarState == KEYGUARD || getExpanded()) {
return false;
}
@@ -727,6 +726,7 @@
}
/** Closes the Qs customizer. */
+ @Override
public void closeQsCustomizer() {
if (mQs != null) {
mQs.closeCustomizer();
@@ -734,7 +734,7 @@
}
/** Returns whether touches from the notification panel should be disallowed */
- public boolean disallowTouches() {
+ boolean disallowTouches() {
if (mQs != null) {
return mQs.disallowPanelTouches();
} else {
@@ -754,15 +754,11 @@
}
}
- public void setDozing(boolean dozing) {
+ void setDozing(boolean dozing) {
mDozing = dozing;
}
- /**
- * This method closes QS but in split shade it should be used only in special cases: to make
- * sure QS closes when shade is closed as well. Otherwise it will result in QS disappearing
- * from split shade
- */
+ @Override
public void closeQs() {
if (mSplitShadeEnabled) {
mShadeLog.d("Closing QS while in split shade");
@@ -794,7 +790,7 @@
}
/** update Qs height state */
- public void setExpansionHeight(float height) {
+ void setExpansionHeight(float height) {
int maxHeight = getMaxExpansionHeight();
height = Math.min(Math.max(
height, getMinExpansionHeight()), maxHeight);
@@ -817,7 +813,7 @@
}
/** */
- public void setHeightOverrideToDesiredHeight() {
+ void setHeightOverrideToDesiredHeight() {
if (isSizeChangeAnimationRunning() && isQsFragmentCreated()) {
mQs.setHeightOverride(mQs.getDesiredHeight());
}
@@ -919,7 +915,7 @@
}
/** Sets Qs ScrimEnabled and updates QS state. */
- public void setScrimEnabled(boolean scrimEnabled) {
+ void setScrimEnabled(boolean scrimEnabled) {
boolean changed = mScrimEnabled != scrimEnabled;
mScrimEnabled = scrimEnabled;
if (changed) {
@@ -995,7 +991,7 @@
}
/** update expanded state of QS */
- public void updateExpansion() {
+ void updateExpansion() {
if (mQs == null) return;
final float squishiness;
if ((isExpandImmediate() || getExpanded()) && !mSplitShadeEnabled) {
@@ -1053,13 +1049,13 @@
// mTransitioningToFullShadeProgress > 0 means we're doing regular lockscreen to shade
// transition. If that's not the case we should follow QS expansion fraction for when
// user is pulling from the same top to go directly to expanded QS
- return getTransitioningToFullShadeProgress() > 0
+ return mTransitioningToFullShadeProgress > 0
? mLockscreenShadeTransitionController.getQSDragProgress()
: computeExpansionFraction();
}
/** */
- public void updateExpansionEnabledAmbient() {
+ void updateExpansionEnabledAmbient() {
final float scrollRangeToTop = mAmbientState.getTopPadding() - mQuickQsHeaderHeight;
mExpansionEnabledAmbient = mSplitShadeEnabled
|| (mAmbientState.getScrollY() <= scrollRangeToTop);
@@ -1081,7 +1077,7 @@
}
/** Calculate fraction of current QS expansion state */
- public float computeExpansionFraction() {
+ float computeExpansionFraction() {
if (mAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
// be larger than 0 because of the timing, leading to flickers.
@@ -1112,7 +1108,7 @@
}
/** Called when shade starts expanding. */
- public void onExpandingStarted(boolean qsFullyExpanded) {
+ void onExpandingStarted(boolean qsFullyExpanded) {
mNotificationStackScrollLayoutController.onExpansionStarted();
mExpandedWhenExpandingStarted = qsFullyExpanded;
mMediaHierarchyManager.setCollapsingShadeFromQS(mExpandedWhenExpandingStarted
@@ -1363,7 +1359,7 @@
}
}
- /** Calculate top padding for notifications */
+ @Override
public float calculateNotificationsTopPadding(boolean isShadeExpanding,
int keyguardNotificationStaticPadding, float expandedFraction) {
float topPadding;
@@ -1404,7 +1400,7 @@
}
}
- /** Calculate height of QS panel */
+ @Override
public int calculatePanelHeightExpanded(int stackScrollerPadding) {
float
notificationHeight =
@@ -1576,7 +1572,6 @@
}
}
- @VisibleForTesting
boolean isTrackingBlocked() {
return mConflictingExpansionGesture && getExpanded();
}
@@ -1591,7 +1586,7 @@
}
/** handles touches in Qs panel area */
- public boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
+ boolean handleTouch(MotionEvent event, boolean isFullyCollapsed,
boolean isShadeOrQsHeightAnimationRunning) {
if (isSplitShadeAndTouchXOutsideQs(event.getX())) {
return false;
@@ -1747,7 +1742,7 @@
}
/** intercepts touches on Qs panel area. */
- public boolean onIntercept(MotionEvent event) {
+ boolean onIntercept(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
pointerIndex = 0;
@@ -1858,7 +1853,7 @@
*
* @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
*/
- public void animateCloseQs(boolean animateAway) {
+ void animateCloseQs(boolean animateAway) {
if (mExpansionAnimator != null) {
if (!mAnimatorExpand) {
return;
@@ -1877,7 +1872,7 @@
}
/** @see #flingQs(float, int, Runnable, boolean) */
- public void flingQs(float vel, int type) {
+ void flingQs(float vel, int type) {
flingQs(vel, type, null /* onFinishRunnable */, false /* isClick */);
}
@@ -2144,7 +2139,7 @@
}
/** */
- public FragmentHostManager.FragmentListener getQsFragmentListener() {
+ FragmentHostManager.FragmentListener getQsFragmentListener() {
return new QsFragmentListener();
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
new file mode 100644
index 0000000..b8250cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapter
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsControllerSceneImpl
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ private val qsSceneAdapter: QSSceneAdapter,
+ private val qsContainerController: QSContainerController,
+) : QuickSettingsController {
+
+ override val expanded: Boolean
+ get() = shadeInteractor.isQsExpanded.value
+
+ override val isCustomizing: Boolean
+ get() = qsSceneAdapter.isCustomizing.value
+
+ @Deprecated("specific to legacy touch handling")
+ override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean {
+ throw UnsupportedOperationException()
+ }
+
+ override fun closeQsCustomizer() {
+ qsContainerController.setCustomizerShowing(false)
+ }
+
+ @Deprecated("specific to legacy split shade")
+ override fun closeQs() {
+ // Do nothing
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculateNotificationsTopPadding(
+ isShadeExpanding: Boolean,
+ keyguardNotificationStaticPadding: Int,
+ expandedFraction: Float
+ ): Float {
+ throw UnsupportedOperationException()
+ }
+
+ @Deprecated("specific to legacy DebugDrawable")
+ override fun calculatePanelHeightExpanded(stackScrollerPadding: Int): Int {
+ throw UnsupportedOperationException()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 5632766..504dbfd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -17,6 +17,8 @@
package com.android.systemui.shade
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.data.repository.PrivacyChipRepository
import com.android.systemui.shade.data.repository.PrivacyChipRepositoryImpl
@@ -111,6 +113,26 @@
sceneContainerOff.get()
}
}
+
+ @Provides
+ @SysUISingleton
+ fun provideQuickSettingsController(
+ sceneContainerFlags: SceneContainerFlags,
+ sceneContainerOn: Provider<QuickSettingsControllerSceneImpl>,
+ sceneContainerOff: Provider<QuickSettingsControllerImpl>,
+ ): QuickSettingsController {
+ return if (sceneContainerFlags.isEnabled()) {
+ sceneContainerOn.get()
+ } else {
+ sceneContainerOff.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun providesQSContainerController(impl: QSSceneAdapterImpl): QSContainerController {
+ return impl
+ }
}
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index ef4e530..a12b970 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -84,6 +84,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -457,14 +458,43 @@
List<KeyboardShortcutMultiMappingGroup> keyboardShortcutMultiMappingGroups =
new ArrayList<>();
for (KeyboardShortcutGroup group : keyboardShortcutGroups) {
- CharSequence categoryTitle = group.getLabel();
- List<ShortcutMultiMappingInfo> shortcutMultiMappingInfos = new ArrayList<>();
- for (KeyboardShortcutInfo info : group.getItems()) {
- shortcutMultiMappingInfos.add(new ShortcutMultiMappingInfo(info));
- }
- keyboardShortcutMultiMappingGroups.add(
+ KeyboardShortcutMultiMappingGroup mappedGroup =
new KeyboardShortcutMultiMappingGroup(
- categoryTitle, shortcutMultiMappingInfos));
+ group.getLabel(),
+ new ArrayList<>());
+ Map<String, List<ShortcutMultiMappingInfo>> shortcutMap = new LinkedHashMap<>();
+ for (KeyboardShortcutInfo info : group.getItems()) {
+ String label = info.getLabel().toString();
+ Icon icon = info.getIcon();
+ if (shortcutMap.containsKey(label)) {
+ List<ShortcutMultiMappingInfo> shortcuts = shortcutMap.get(label);
+ boolean foundSameIcon = false;
+ for (ShortcutMultiMappingInfo shortcut : shortcuts) {
+ Icon shortcutIcon = shortcut.getIcon();
+ if ((shortcutIcon != null
+ && icon != null
+ && shortcutIcon.sameAs(icon))
+ || (shortcutIcon == null && icon == null)) {
+ foundSameIcon = true;
+ shortcut.addShortcutKeyGroup(new ShortcutKeyGroup(info, null));
+ break;
+ }
+ }
+ if (!foundSameIcon) {
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ }
+ } else {
+ List<ShortcutMultiMappingInfo> shortcuts = new ArrayList<>();
+ shortcuts.add(new ShortcutMultiMappingInfo(info));
+ shortcutMap.put(label, shortcuts);
+ }
+ }
+ for (List<ShortcutMultiMappingInfo> shortcutInfos : shortcutMap.values()) {
+ for (ShortcutMultiMappingInfo shortcutInfo : shortcutInfos) {
+ mappedGroup.addItem(shortcutInfo);
+ }
+ }
+ keyboardShortcutMultiMappingGroups.add(mappedGroup);
}
return keyboardShortcutMultiMappingGroups;
}
@@ -1377,7 +1407,9 @@
ShortcutMultiMappingInfo(KeyboardShortcutInfo info) {
mLabel = info.getLabel();
mIcon = info.getIcon();
- mShortcutKeyGroups = Arrays.asList(new ShortcutKeyGroup(info, null));
+ mShortcutKeyGroups = new ArrayList<>(
+ Arrays.asList(new ShortcutKeyGroup(info, null))
+ );
}
CharSequence getLabel() {
@@ -1388,6 +1420,10 @@
return mIcon;
}
+ void addShortcutKeyGroup(ShortcutKeyGroup group) {
+ mShortcutKeyGroups.add(group);
+ }
+
List<ShortcutKeyGroup> getShortcutKeyGroups() {
return mShortcutKeyGroups;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4ee8349..81f644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -352,10 +352,6 @@
/** Called by the touch helper when the drag down was aborted and should be reset. */
internal fun onDragDownReset() {
logger.logDragDownAborted()
- nsslController.setDimmed(
- /* dimmed= */ true,
- /* animate= */ true,
- )
nsslController.resetScrollPosition()
nsslController.resetCheckSnoozeLeavebehind()
setDragDownAmountAnimated(0f)
@@ -366,12 +362,7 @@
*
* @param above whether they dragged above it
*/
- internal fun onCrossedThreshold(above: Boolean) {
- nsslController.setDimmed(
- /* dimmed= */ !above,
- /* animate= */ true,
- )
- }
+ internal fun onCrossedThreshold(above: Boolean) {}
/** Called by the touch helper when the drag down was started */
internal fun onDragDownStarted(startingChild: ExpandableView?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 1a06eec..0091bc5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -420,7 +420,7 @@
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- if (allowPrivateProfile()){
+ if (privateSpaceFlagsEnabled()) {
filter.addAction(Intent.ACTION_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
}
@@ -813,13 +813,17 @@
}
private boolean profileAvailabilityActions(String action){
- return allowPrivateProfile()?
+ return privateSpaceFlagsEnabled()?
Objects.equals(action,Intent.ACTION_PROFILE_AVAILABLE)||
Objects.equals(action,Intent.ACTION_PROFILE_UNAVAILABLE):
Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_AVAILABLE)||
Objects.equals(action,Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
}
+ private static boolean privateSpaceFlagsEnabled() {
+ return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+ }
+
@Override
public void dump(PrintWriter pw, String[] args) {
pw.println("NotificationLockscreenUserManager state:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
index 642eacc..c4d9cbf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt
@@ -35,6 +35,10 @@
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor
import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel
+import com.android.systemui.qs.tiles.impl.internet.domain.InternetTileMapper
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileDataInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.interactor.InternetTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.internet.domain.model.InternetTileModel
import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor
import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor
@@ -90,6 +94,7 @@
const val AIRPLANE_MODE_TILE_SPEC = "airplane"
const val DATA_SAVER_TILE_SPEC = "saver"
+ const val INTERNET_TILE_SPEC = "internet"
/** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */
@Provides
@@ -168,5 +173,36 @@
stateInteractor,
mapper,
)
+
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+ QSTileConfig(
+ tileSpec = TileSpec.create(INTERNET_TILE_SPEC),
+ uiConfig =
+ QSTileUIConfig.Resource(
+ iconRes = R.drawable.ic_qs_no_internet_available,
+ labelRes = R.string.quick_settings_internet_label,
+ ),
+ instanceId = uiEventLogger.getNewInstanceId(),
+ )
+
+ /** Inject InternetTile into tileViewModelMap in QSModule */
+ @Provides
+ @IntoMap
+ @StringKey(INTERNET_TILE_SPEC)
+ fun provideInternetTileViewModel(
+ factory: QSTileViewModelFactory.Static<InternetTileModel>,
+ mapper: InternetTileMapper,
+ stateInteractor: InternetTileDataInteractor,
+ userActionInteractor: InternetTileUserActionInteractor
+ ): QSTileViewModel =
+ factory.create(
+ TileSpec.create(INTERNET_TILE_SPEC),
+ userActionInteractor,
+ stateInteractor,
+ mapper,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
new file mode 100644
index 0000000..1bebbfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/StatusBarKeyguardViewManagerInteractor.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Whether to set the status bar keyguard view occluded or not, and whether to animate that change.
+ */
+data class OccludedState(
+ val occluded: Boolean,
+ val animate: Boolean = false,
+)
+
+/** Handles logic around calls to [StatusBarKeyguardViewManager] in legacy code. */
+@Deprecated("Will be removed once all of SBKVM's responsibilies are refactored.")
+@SysUISingleton
+class StatusBarKeyguardViewManagerInteractor
+@Inject
+constructor(
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ powerInteractor: PowerInteractor,
+) {
+ /** Occlusion state to apply whenever a keyguard transition is STARTED, if any. */
+ private val occlusionStateFromStartedStep: Flow<OccludedState> =
+ keyguardTransitionInteractor.startedKeyguardTransitionStep
+ .sample(powerInteractor.detailedWakefulness, ::Pair)
+ .map { (startedStep, wakefulness) ->
+ val transitioningFromPowerButtonGesture =
+ KeyguardState.deviceIsAsleepInState(startedStep.from) &&
+ startedStep.to == KeyguardState.OCCLUDED &&
+ wakefulness.powerButtonLaunchGestureTriggered
+
+ if (
+ startedStep.to == KeyguardState.OCCLUDED && !transitioningFromPowerButtonGesture
+ ) {
+ // Set occluded upon STARTED, *unless* we're transitioning from the power
+ // button, in which case we're going to play an animation over the lockscreen UI
+ // and need to remain unoccluded until the transition finishes.
+ return@map OccludedState(occluded = true, animate = false)
+ }
+
+ if (
+ startedStep.from == KeyguardState.OCCLUDED &&
+ startedStep.to == KeyguardState.LOCKSCREEN
+ ) {
+ // Set unoccluded immediately ONLY if we're transitioning back to the lockscreen
+ // since we need the views visible to animate them back down. This is a special
+ // case due to the way unocclusion remote animations are run. We can remove this
+ // once the unocclude animation uses the return animation framework.
+ return@map OccludedState(occluded = false, animate = false)
+ }
+
+ // Otherwise, wait for the transition to FINISH to decide.
+ return@map null
+ }
+ .filterNotNull()
+
+ /** Occlusion state to apply whenever a keyguard transition is FINISHED. */
+ private val occlusionStateFromFinishedStep =
+ keyguardTransitionInteractor.finishedKeyguardTransitionStep
+ .sample(keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop, ::Pair)
+ .map { (finishedStep, showWhenLockedOnTop) ->
+ // If we're FINISHED in OCCLUDED, we want to render as occluded. We also need to
+ // remain occluded if a SHOW_WHEN_LOCKED activity is on the top of the task stack,
+ // and we're in any state other than GONE. This is necessary, for example, when we
+ // transition from OCCLUDED to a bouncer state. Otherwise, we should not be
+ // occluded.
+ val occluded =
+ finishedStep.to == KeyguardState.OCCLUDED ||
+ (showWhenLockedOnTop && finishedStep.to != KeyguardState.GONE)
+ OccludedState(occluded = occluded, animate = false)
+ }
+
+ /** Occlusion state to apply to SKBVM's setOccluded call. */
+ val keyguardViewOcclusionState =
+ merge(occlusionStateFromStartedStep, occlusionStateFromFinishedStep)
+ .distinctUntilChangedBy {
+ // Don't switch 'animate' values mid-transition.
+ it.occluded
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index ea9df9a..05e8717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -275,15 +275,6 @@
return getHeight();
}
- /**
- * Sets the notification as dimmed. The default implementation does nothing.
- *
- * @param dimmed Whether the notification should be dimmed.
- * @param fade Whether an animation should be played to change the state.
- */
- public void setDimmed(boolean dimmed, boolean fade) {
- }
-
public boolean isRemoved() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
index dab89c5..b90aa10 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt
@@ -19,7 +19,9 @@
import android.widget.flags.Flags.notifLinearlayoutOptimized
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
import javax.inject.Inject
+import javax.inject.Provider
interface NotifRemoteViewsFactoryContainer {
val factories: Set<NotifRemoteViewsFactory>
@@ -31,7 +33,8 @@
featureFlags: FeatureFlags,
precomputedTextViewFactory: PrecomputedTextViewFactory,
bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory,
- optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory
+ optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory,
+ notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>,
) : NotifRemoteViewsFactoryContainer {
override val factories: Set<NotifRemoteViewsFactory> = buildSet {
add(precomputedTextViewFactory)
@@ -41,5 +44,8 @@
if (notifLinearlayoutOptimized()) {
add(optimizedLinearLayoutFactory)
}
+ if (NotificationViewFlipperPausing.isEnabled) {
+ add(notificationViewFlipperFactory.get())
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index d308fa5..f835cca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,6 +42,7 @@
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import com.android.app.tracing.TraceUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.dagger.SysUISingleton;
@@ -369,49 +370,55 @@
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
NotificationContentInflaterLogger logger) {
- InflationProgress result = new InflationProgress();
- final NotificationEntry entryForLogging = row.getEntry();
+ return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
+ InflationProgress result = new InflationProgress();
+ final NotificationEntry entryForLogging = row.getEntry();
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
- result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
- result.newExpandedView = createExpandedView(builder, isLowPriority);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
- result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
- result.newPublicView = builder.makePublicContentView(isLowPriority);
- }
-
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating group summary remote view");
- result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating contracted remote view");
+ result.newContentView = createContentView(builder, isLowPriority,
+ usesIncreasedHeight);
}
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- logger.logAsyncTaskProgress(entryForLogging,
- "creating low-priority group summary remote view");
- result.mNewLowPriorityGroupHeaderView =
- builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating expanded remote view");
+ result.newExpandedView = createExpandedView(builder, isLowPriority);
}
- }
- setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
- result.packageContext = packageContext;
- result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
- result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
- true /* showingPublic */);
- return result;
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating heads up remote view");
+ result.newHeadsUpView = builder.createHeadsUpContentView(
+ usesIncreasedHeadsUpHeight);
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging, "creating public remote view");
+ result.newPublicView = builder.makePublicContentView(isLowPriority);
+ }
+
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating group summary remote view");
+ result.mNewGroupHeaderView = builder.makeNotificationGroupHeader();
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ logger.logAsyncTaskProgress(entryForLogging,
+ "creating low-priority group summary remote view");
+ result.mNewLowPriorityGroupHeaderView =
+ builder.makeLowPriorityContentView(true /* useRegularSubtext */);
+ }
+ }
+ setNotifsViewsInflaterFactory(result, row, notifLayoutInflaterFactoryProvider);
+ result.packageContext = packageContext;
+ result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(
+ false /* showingPublic */);
+ result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
+ true /* showingPublic */);
+
+ return result;
+ });
}
private static void setNotifsViewsInflaterFactory(InflationProgress result,
@@ -445,6 +452,8 @@
RemoteViews.InteractionHandler remoteViewClickHandler,
@Nullable InflationCallback callback,
NotificationContentInflaterLogger logger) {
+ Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
@@ -621,6 +630,7 @@
cancellationSignal.setOnCancelListener(
() -> {
logger.logAsyncTaskProgress(entry, "apply cancelled");
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
runningInflations.values().forEach(CancellationSignal::cancel);
});
@@ -769,17 +779,17 @@
if (!requiresHeightCheck(entry)) {
return true;
}
- Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement");
- int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- int referenceWidth = resources.getDimensionPixelSize(
- R.dimen.notification_validation_reference_width);
- int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY);
- view.measure(widthSpec, heightSpec);
- int minHeight = resources.getDimensionPixelSize(
- R.dimen.notification_validation_minimum_allowed_height);
- boolean result = view.getMeasuredHeight() >= minHeight;
- Trace.endSection();
- return result;
+ return TraceUtils.trace("NotificationContentInflater#satisfiesMinHeightRequirement", () -> {
+ int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ int referenceWidth = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_reference_width);
+ int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth,
+ View.MeasureSpec.EXACTLY);
+ view.measure(widthSpec, heightSpec);
+ int minHeight = resources.getDimensionPixelSize(
+ R.dimen.notification_validation_minimum_allowed_height);
+ return view.getMeasuredHeight() >= minHeight;
+ });
}
/**
@@ -833,143 +843,144 @@
@Nullable InflationCallback endListener, NotificationEntry entry,
ExpandableNotificationRow row, NotificationContentInflaterLogger logger) {
Assert.isMainThread();
+ if (!runningInflations.isEmpty()) {
+ return false;
+ }
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
- if (runningInflations.isEmpty()) {
- logger.logAsyncTaskProgress(entry, "finishing");
- boolean setRepliesAndActions = true;
- if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
- if (result.inflatedContentView != null) {
- // New view case
- privateLayout.setContractedChild(result.inflatedContentView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
- // Reinflation case. Only update if it's still cached (i.e. view has not been
- // freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
- result.newContentView);
- }
- setRepliesAndActions = true;
+ logger.logAsyncTaskProgress(entry, "finishing");
+ boolean setRepliesAndActions = true;
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
+ if (result.inflatedContentView != null) {
+ // New view case
+ privateLayout.setContractedChild(result.inflatedContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
+ // Reinflation case. Only update if it's still cached (i.e. view has not been
+ // freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
- if (result.inflatedExpandedView != null) {
- privateLayout.setExpandedChild(result.inflatedExpandedView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- } else if (result.newExpandedView == null) {
- privateLayout.setExpandedChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
- result.newExpandedView);
- }
- if (result.newExpandedView != null) {
- privateLayout.setExpandedInflatedSmartReplies(
- result.expandedInflatedSmartReplies);
- } else {
- privateLayout.setExpandedInflatedSmartReplies(null);
- }
- row.setExpandable(result.newExpandedView != null);
- setRepliesAndActions = true;
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
- if (result.inflatedHeadsUpView != null) {
- privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- } else if (result.newHeadsUpView == null) {
- privateLayout.setHeadsUpChild(null);
- remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
- result.newHeadsUpView);
- }
- if (result.newHeadsUpView != null) {
- privateLayout.setHeadsUpInflatedSmartReplies(
- result.headsUpInflatedSmartReplies);
- } else {
- privateLayout.setHeadsUpInflatedSmartReplies(null);
- }
- setRepliesAndActions = true;
- }
-
- if (AsyncHybridViewInflation.isEnabled()
- && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
- HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
- SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
- if (viewHolder != null && viewModel != null) {
- if (viewModel.isConversation()) {
- SingleLineConversationViewBinder.bind(
- result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder
- );
- } else {
- SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
- result.mInflatedSingleLineViewHolder);
- }
- privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
- }
- }
-
- if (setRepliesAndActions) {
- privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
- }
-
- if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
- if (result.inflatedPublicView != null) {
- publicLayout.setContractedChild(result.inflatedPublicView);
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
- remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
- result.newPublicView);
- }
- }
-
- if (AsyncGroupHeaderViewInflation.isEnabled()) {
- if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedGroupHeaderView != null) {
- row.setIsLowPriority(false);
- row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
-
- if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
- if (result.mInflatedLowPriorityGroupHeaderView != null) {
- // New view case, set row to low priority
- row.setIsLowPriority(true);
- row.setLowPriorityGroupHeader(
- /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewLowPriorityGroupHeaderView);
- } else if (remoteViewCache.hasCachedView(entry,
- FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
- // Re-inflation case. Only update if it's still cached (i.e. view has not
- // been freed while inflating).
- remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
- result.mNewGroupHeaderView);
- }
- }
- }
-
- entry.headsUpStatusBarText = result.headsUpStatusBarText;
- entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
- if (endListener != null) {
- endListener.onAsyncInflationFinished(entry);
- }
- return true;
+ setRepliesAndActions = true;
}
- return false;
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
+ if (result.inflatedExpandedView != null) {
+ privateLayout.setExpandedChild(result.inflatedExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ } else if (result.newExpandedView == null) {
+ privateLayout.setExpandedChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
+ }
+ if (result.newExpandedView != null) {
+ privateLayout.setExpandedInflatedSmartReplies(
+ result.expandedInflatedSmartReplies);
+ } else {
+ privateLayout.setExpandedInflatedSmartReplies(null);
+ }
+ row.setExpandable(result.newExpandedView != null);
+ setRepliesAndActions = true;
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+ if (result.inflatedHeadsUpView != null) {
+ privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ } else if (result.newHeadsUpView == null) {
+ privateLayout.setHeadsUpChild(null);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
+ }
+ if (result.newHeadsUpView != null) {
+ privateLayout.setHeadsUpInflatedSmartReplies(
+ result.headsUpInflatedSmartReplies);
+ } else {
+ privateLayout.setHeadsUpInflatedSmartReplies(null);
+ }
+ setRepliesAndActions = true;
+ }
+
+ if (AsyncHybridViewInflation.isEnabled()
+ && (reInflateFlags & FLAG_CONTENT_VIEW_SINGLE_LINE) != 0) {
+ HybridNotificationView viewHolder = result.mInflatedSingleLineViewHolder;
+ SingleLineViewModel viewModel = result.mInflatedSingleLineViewModel;
+ if (viewHolder != null && viewModel != null) {
+ if (viewModel.isConversation()) {
+ SingleLineConversationViewBinder.bind(
+ result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder
+ );
+ } else {
+ SingleLineViewBinder.bind(result.mInflatedSingleLineViewModel,
+ result.mInflatedSingleLineViewHolder);
+ }
+ privateLayout.setSingleLineView(result.mInflatedSingleLineViewHolder);
+ }
+ }
+
+ if (setRepliesAndActions) {
+ privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
+ }
+
+ if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
+ if (result.inflatedPublicView != null) {
+ publicLayout.setContractedChild(result.inflatedPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ }
+ }
+
+ if (AsyncGroupHeaderViewInflation.isEnabled()) {
+ if ((reInflateFlags & FLAG_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedGroupHeaderView != null) {
+ row.setIsLowPriority(false);
+ row.setGroupHeader(/* headerView= */ result.mInflatedGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+
+ if ((reInflateFlags & FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER) != 0) {
+ if (result.mInflatedLowPriorityGroupHeaderView != null) {
+ // New view case, set row to low priority
+ row.setIsLowPriority(true);
+ row.setLowPriorityGroupHeader(
+ /* headerView= */ result.mInflatedLowPriorityGroupHeaderView);
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewLowPriorityGroupHeaderView);
+ } else if (remoteViewCache.hasCachedView(entry,
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)) {
+ // Re-inflation case. Only update if it's still cached (i.e. view has not
+ // been freed while inflating).
+ remoteViewCache.putCachedView(entry, FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ result.mNewGroupHeaderView);
+ }
+ }
+ }
+
+ entry.headsUpStatusBarText = result.headsUpStatusBarText;
+ entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
+ Trace.endAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
+ if (endListener != null) {
+ endListener.onAsyncInflationFinished(entry);
+ }
+ return true;
}
private static RemoteViews createExpandedView(Notification.Builder builder,
@@ -1102,83 +1113,97 @@
}
@Override
+ protected void onPreExecute() {
+ Trace.beginAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
+ @Override
protected InflationProgress doInBackground(Void... params) {
- try {
- final StatusBarNotification sbn = mEntry.getSbn();
- // Ensure the ApplicationInfo is updated before a builder is recovered.
- updateApplicationInfo(sbn);
- final Notification.Builder recoveredBuilder
- = Notification.Builder.recoverBuilder(mContext,
- sbn.getNotification());
+ return TraceUtils.trace("NotificationContentInflater.AsyncInflationTask#doInBackground",
+ () -> {
+ try {
+ return doInBackgroundInternal();
+ } catch (Exception e) {
+ mError = e;
+ mLogger.logAsyncTaskException(mEntry, "inflating", e);
+ return null;
+ }
+ });
+ }
- Context packageContext = sbn.getPackageContext(mContext);
- if (recoveredBuilder.usesTemplate()) {
- // For all of our templates, we want it to be RTL
- packageContext = new RtlEnabledContext(packageContext);
- }
- boolean isConversation = mEntry.getRanking().isConversation();
- Notification.MessagingStyle messagingStyle = null;
- if (isConversation) {
- messagingStyle = mConversationProcessor.processNotification(
- mEntry, recoveredBuilder, mLogger);
- }
- InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
- recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
- mUsesIncreasedHeadsUpHeight, packageContext, mRow,
- mNotifLayoutInflaterFactoryProvider, mLogger);
+ private InflationProgress doInBackgroundInternal() {
+ final StatusBarNotification sbn = mEntry.getSbn();
+ // Ensure the ApplicationInfo is updated before a builder is recovered.
+ updateApplicationInfo(sbn);
+ final Notification.Builder recoveredBuilder = Notification.Builder.recoverBuilder(
+ mContext, sbn.getNotification());
- mLogger.logAsyncTaskProgress(mEntry,
- "getting existing smart reply state (on wrong thread!)");
- InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
- mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
- InflationProgress result = inflateSmartReplyViews(
- /* result = */ inflationProgress,
- mReInflateFlags,
- mEntry,
- mContext,
- packageContext,
- previousSmartReplyState,
- mSmartRepliesInflater,
- mLogger);
-
- if (AsyncHybridViewInflation.isEnabled()) {
- // Inflate the single-line content view's ViewModel and ViewHolder from the
- // background thread, the ViewHolder needs to be bind with ViewModel later from
- // the main thread.
- result.mInflatedSingleLineViewModel = SingleLineViewInflater
- .inflateSingleLineViewModel(
- mEntry.getSbn().getNotification(),
- messagingStyle,
- recoveredBuilder,
- mContext
- );
- result.mInflatedSingleLineViewHolder =
- SingleLineViewInflater.inflateSingleLineViewHolder(
- isConversation,
- mReInflateFlags,
- mEntry,
- mContext,
- mLogger
- );
- }
-
- mLogger.logAsyncTaskProgress(mEntry,
- "getting row image resolver (on wrong thread!)");
- final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
- // wait for image resolver to finish preloading
- mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
- imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
-
- return result;
- } catch (Exception e) {
- mError = e;
- mLogger.logAsyncTaskException(mEntry, "inflating", e);
- return null;
+ Context packageContext = sbn.getPackageContext(mContext);
+ if (recoveredBuilder.usesTemplate()) {
+ // For all of our templates, we want it to be RTL
+ packageContext = new RtlEnabledContext(packageContext);
}
+ boolean isConversation = mEntry.getRanking().isConversation();
+ Notification.MessagingStyle messagingStyle = null;
+ if (isConversation) {
+ messagingStyle = mConversationProcessor.processNotification(
+ mEntry, recoveredBuilder, mLogger);
+ }
+ InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
+ recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
+ mUsesIncreasedHeadsUpHeight, packageContext, mRow,
+ mNotifLayoutInflaterFactoryProvider, mLogger);
+
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting existing smart reply state (on wrong thread!)");
+ InflatedSmartReplyState previousSmartReplyState =
+ mRow.getExistingSmartReplyState();
+ mLogger.logAsyncTaskProgress(mEntry, "inflating smart reply views");
+ InflationProgress result = inflateSmartReplyViews(
+ /* result = */ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ previousSmartReplyState,
+ mSmartRepliesInflater,
+ mLogger);
+
+ if (AsyncHybridViewInflation.isEnabled()) {
+ // Inflate the single-line content view's ViewModel and ViewHolder from the
+ // background thread, the ViewHolder needs to be bind with ViewModel later from
+ // the main thread.
+ result.mInflatedSingleLineViewModel = SingleLineViewInflater
+ .inflateSingleLineViewModel(
+ mEntry.getSbn().getNotification(),
+ messagingStyle,
+ recoveredBuilder,
+ mContext
+ );
+ result.mInflatedSingleLineViewHolder =
+ SingleLineViewInflater.inflateSingleLineViewHolder(
+ isConversation,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ mLogger
+ );
+ }
+
+ mLogger.logAsyncTaskProgress(mEntry,
+ "getting row image resolver (on wrong thread!)");
+ final NotificationInlineImageResolver imageResolver = mRow.getImageResolver();
+ // wait for image resolver to finish preloading
+ mLogger.logAsyncTaskProgress(mEntry, "waiting for preloaded images");
+ imageResolver.waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+ return result;
}
@Override
protected void onPostExecute(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+
if (mError == null) {
// Logged in detail in apply.
mCancellationSignal = apply(
@@ -1197,6 +1222,11 @@
}
}
+ @Override
+ protected void onCancelled(InflationProgress result) {
+ Trace.endAsyncSection(ASYNC_TASK_TRACE_METHOD, System.identityHashCode(this));
+ }
+
private void handleError(Exception e) {
mEntry.onInflationTaskFinished();
StatusBarNotification sbn = mEntry.getSbn();
@@ -1294,4 +1324,8 @@
public abstract void setResultView(View v);
public abstract RemoteViews getRemoteView();
}
+
+ private static final String ASYNC_TASK_TRACE_METHOD =
+ "NotificationContentInflater.AsyncInflationTask";
+ private static final String APPLY_TRACE_METHOD = "NotificationContentInflater#apply";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
new file mode 100644
index 0000000..0594c12
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationViewFlipperFactory.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import android.widget.ViewFlipper
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
+import com.android.systemui.statusbar.notification.row.ui.viewbinder.NotificationViewFlipperBinder
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import javax.inject.Inject
+
+/**
+ * A factory which owns the construction of any ViewFlipper inside of Notifications, and binds it
+ * with a view model. This ensures that ViewFlippers are paused when the keyguard is showing.
+ */
+class NotificationViewFlipperFactory
+@Inject
+constructor(
+ private val viewModel: NotificationViewFlipperViewModel,
+) : NotifRemoteViewsFactory {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ override fun instantiate(
+ row: ExpandableNotificationRow,
+ @InflationFlag layoutType: Int,
+ parent: View?,
+ name: String,
+ context: Context,
+ attrs: AttributeSet
+ ): View? {
+ return when (name) {
+ ViewFlipper::class.java.name,
+ ViewFlipper::class.java.simpleName ->
+ ViewFlipper(context, attrs).also { viewFlipper ->
+ NotificationViewFlipperBinder.bindWhileAttached(viewFlipper, viewModel)
+ }
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 9445d56..ea3036e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -31,6 +31,7 @@
import com.android.systemui.res.R;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.time.SystemClock;
import javax.inject.Inject;
@@ -46,9 +47,14 @@
private NotificationEntry mEntry;
private boolean mCancelled;
private Throwable mInflateOrigin;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
+ private long mInflateStartTimeMs;
@Inject
- public RowInflaterTask() {
+ public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger) {
+ mSystemClock = systemClock;
+ mLogger = logger;
}
/**
@@ -61,29 +67,49 @@
}
mListener = listener;
AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
- ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+ ? new AsyncLayoutInflater(context, makeRowInflater(entry))
: new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
+
+ mLogger.logInflateStart(entry);
+ mInflateStartTimeMs = mSystemClock.elapsedRealtime();
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
+ private RowAsyncLayoutInflater makeRowInflater(NotificationEntry entry) {
+ return new RowAsyncLayoutInflater(entry, mSystemClock, mLogger);
+ }
+
@VisibleForTesting
static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
private final NotificationEntry mEntry;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mLogger;
- RowAsyncLayoutInflater(NotificationEntry entry) {
+ RowAsyncLayoutInflater(NotificationEntry entry, SystemClock systemClock,
+ RowInflaterTaskLogger logger) {
mEntry = entry;
+ mSystemClock = systemClock;
+ mLogger = logger;
}
@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
- if (name.equals(ExpandableNotificationRow.class.getName())) {
- return new ExpandableNotificationRow(context, attrs, mEntry);
+ if (!name.equals(ExpandableNotificationRow.class.getName())) {
+ return null;
}
- return null;
+
+ final long startMs = mSystemClock.elapsedRealtime();
+ final ExpandableNotificationRow row =
+ new ExpandableNotificationRow(context, attrs, mEntry);
+ final long elapsedMs = mSystemClock.elapsedRealtime() - startMs;
+
+ mLogger.logCreatedRow(mEntry, elapsedMs);
+
+ return row;
}
@Nullable
@@ -101,6 +127,9 @@
@Override
public void onInflateFinished(View view, int resid, ViewGroup parent) {
+ final long elapsedMs = mSystemClock.elapsedRealtime() - mInflateStartTimeMs;
+ mLogger.logInflateFinish(mEntry, elapsedMs, mCancelled);
+
if (!mCancelled) {
try {
mEntry.onInflationTaskFinished();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
new file mode 100644
index 0000000..94da6be
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTaskLogger.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.NotifInflationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import javax.inject.Inject
+
+class RowInflaterTaskLogger @Inject constructor(@NotifInflationLog private val buffer: LogBuffer) {
+ fun logInflateStart(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ { str1 = entry.logKey },
+ { "started row inflation for $str1" }
+ )
+ }
+
+ fun logCreatedRow(entry: NotificationEntry, elapsedMs: Long) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ },
+ { "created row in $long1 ms for $str1" }
+ )
+ }
+
+ fun logInflateFinish(entry: NotificationEntry, elapsedMs: Long, cancelled: Boolean) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = entry.logKey
+ long1 = elapsedMs
+ bool1 = cancelled
+ },
+ { "finished ${if (bool1) "cancelled " else ""}row inflation in $long1 ms for $str1" }
+ )
+ }
+}
+
+private const val TAG = "RowInflaterTask"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
new file mode 100644
index 0000000..133d3e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/NotificationViewFlipperBinder.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewbinder
+
+import android.widget.ViewFlipper
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.notification.row.ui.viewmodel.NotificationViewFlipperViewModel
+import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+
+/** Binds a [NotificationViewFlipper] to its [view model][NotificationViewFlipperViewModel]. */
+object NotificationViewFlipperBinder {
+ fun bindWhileAttached(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ): DisposableHandle {
+ if (viewFlipper.isAutoStart) {
+ // If the ViewFlipper is not set to AutoStart, the pause binding is meaningless
+ return DisposableHandle {}
+ }
+ return viewFlipper.repeatWhenAttached {
+ lifecycleScope.launch { bind(viewFlipper, viewModel) }
+ }
+ }
+
+ suspend fun bind(
+ viewFlipper: ViewFlipper,
+ viewModel: NotificationViewFlipperViewModel,
+ ) = coroutineScope { launch { viewModel.isPaused.collect { viewFlipper.setPaused(it) } } }
+
+ private fun ViewFlipper.setPaused(paused: Boolean) {
+ if (paused) {
+ stopFlipping()
+ } else if (isAutoStart) {
+ startFlipping()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
new file mode 100644
index 0000000..7694e58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackInteractor
+import com.android.systemui.util.kotlin.FlowDumperImpl
+import javax.inject.Inject
+
+/** A model which represents whether ViewFlippers inside notifications should be paused. */
+@SysUISingleton
+class NotificationViewFlipperViewModel
+@Inject
+constructor(
+ dumpManager: DumpManager,
+ stackInteractor: NotificationStackInteractor,
+) : FlowDumperImpl(dumpManager) {
+ init {
+ /* check if */ NotificationViewFlipperPausing.isUnexpectedlyInLegacyMode()
+ }
+
+ val isPaused = stackInteractor.isShowingOnLockscreen.dumpWhileCollecting("isPaused")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
new file mode 100644
index 0000000..cea6a2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationViewFlipperPausing.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification view flipper pausing flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationViewFlipperPausing {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_VIEW_FLIPPER_PAUSING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationViewFlipperPausing()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index c90acee..ab2f664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -61,7 +61,6 @@
*/
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private int mScrollY;
- private boolean mDimmed;
private float mOverScrollTopAmount;
private float mOverScrollBottomAmount;
private boolean mDozing;
@@ -344,14 +343,6 @@
this.mScrollY = Math.max(scrollY, 0);
}
- /**
- * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
- * translucent and everything is scaled back a bit.
- */
- public void setDimmed(boolean dimmed) {
- mDimmed = dimmed;
- }
-
/** While dozing, we draw as little as possible, assuming a black background */
public void setDozing(boolean dozing) {
mDozing = dozing;
@@ -375,12 +366,6 @@
mHideSensitive = hideSensitive;
}
- public boolean isDimmed() {
- // While we are expanding from pulse, we want the notifications not to be dimmed, otherwise
- // you'd see the difference to the pulsing notification
- return mDimmed && !(isPulseExpanding() && mDozeAmount == 1.0f);
- }
-
public boolean isDozing() {
return mDozing;
}
@@ -768,7 +753,6 @@
pw.println("mHideSensitive=" + mHideSensitive);
pw.println("mShadeExpanded=" + mShadeExpanded);
pw.println("mClearAllInProgress=" + mClearAllInProgress);
- pw.println("mDimmed=" + mDimmed);
pw.println("mStatusBarState=" + mStatusBarState);
pw.println("mExpansionChanging=" + mExpansionChanging);
pw.println("mPanelFullWidth=" + mIsSmallScreen);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
index 5343cbf..03a1082 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationFilter.java
@@ -35,7 +35,6 @@
boolean animateZ;
boolean animateHeight;
boolean animateTopInset;
- boolean animateDimmed;
boolean animateHideSensitive;
boolean hasDelays;
boolean hasGoToFullShadeEvent;
@@ -83,11 +82,6 @@
return this;
}
- public AnimationFilter animateDimmed() {
- animateDimmed = true;
- return this;
- }
-
public AnimationFilter animateHideSensitive() {
animateHideSensitive = true;
return this;
@@ -128,7 +122,6 @@
animateZ |= filter.animateZ;
animateHeight |= filter.animateHeight;
animateTopInset |= filter.animateTopInset;
- animateDimmed |= filter.animateDimmed;
animateHideSensitive |= filter.animateHideSensitive;
hasDelays |= filter.hasDelays;
mAnimatedProperties.addAll(filter.mAnimatedProperties);
@@ -142,7 +135,6 @@
animateZ = false;
animateHeight = false;
animateTopInset = false;
- animateDimmed = false;
animateHideSensitive = false;
hasDelays = false;
hasGoToFullShadeEvent = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d0c5c82..d1e5ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -88,7 +88,6 @@
| ExpandableViewState.LOCATION_MAIN_AREA;
public int height;
- public boolean dimmed;
public boolean hideSensitive;
public boolean belowSpeedBump;
public boolean inShelf;
@@ -128,7 +127,6 @@
if (viewState instanceof ExpandableViewState) {
ExpandableViewState svs = (ExpandableViewState) viewState;
height = svs.height;
- dimmed = svs.dimmed;
hideSensitive = svs.hideSensitive;
belowSpeedBump = svs.belowSpeedBump;
clipTopAmount = svs.clipTopAmount;
@@ -155,9 +153,6 @@
expandableView.setActualHeight(newHeight, false /* notifyListeners */);
}
- // apply dimming
- expandableView.setDimmed(this.dimmed, false /* animate */);
-
// apply hiding sensitive
expandableView.setHideSensitive(
this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
@@ -216,9 +211,6 @@
abortAnimation(child, TAG_ANIMATOR_BOTTOM_INSET);
}
- // start dimmed animation
- expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
-
// apply below the speed bump
if (!NotificationIconContainerRefactor.isEnabled()) {
expandableView.setBelowSpeedBump(this.belowSpeedBump);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index fa97300..28f874d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -795,7 +795,6 @@
} else {
childState.setZTranslation(0);
}
- childState.dimmed = parentState.dimmed;
childState.hideSensitive = parentState.hideSensitive;
childState.belowSpeedBump = parentState.belowSpeedBump;
childState.clipTopAmount = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 27db84f..b47b18d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -17,6 +17,8 @@
package com.android.systemui.statusbar.notification.stack;
import static android.os.Trace.TRACE_TAG_APP;
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_UP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
@@ -29,10 +31,6 @@
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeAnimator;
-import android.animation.ValueAnimator;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
@@ -79,7 +77,6 @@
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -163,18 +160,11 @@
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
- /**
- * The distance in pixels between sections when the sections are directly adjacent (no visible
- * gap is drawn between them). In this case we don't want to round their corners.
- */
- private static final int DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX = 1;
private boolean mKeyguardBypassEnabled;
private final ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private int mCurrentStackHeight = Integer.MAX_VALUE;
- private final Paint mBackgroundPaint = new Paint();
- private final boolean mShouldDrawNotificationBackground;
private boolean mHighPriorityBeforeSpeedBump;
private float mExpandedHeight;
@@ -263,7 +253,6 @@
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
- private boolean mDimmedNeedsAnimation;
private boolean mHideSensitiveNeedsAnimation;
private boolean mActivateNeedsAnimation;
private boolean mGoToFullShadeNeedsAnimation;
@@ -350,40 +339,15 @@
}
};
private final NotificationSection[] mSections;
- private boolean mAnimateNextBackgroundTop;
- private boolean mAnimateNextBackgroundBottom;
- private boolean mAnimateNextSectionBoundsChange;
- private @ColorInt int mBgColor;
- private float mDimAmount;
- private ValueAnimator mDimAnimator;
private final ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
- private final Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDimAnimator = null;
- }
- };
- private final ValueAnimator.AnimatorUpdateListener mDimUpdateListener
- = new ValueAnimator.AnimatorUpdateListener() {
-
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- setDimAmount((Float) animation.getAnimatedValue());
- }
- };
protected ViewGroup mQsHeader;
// Rect of QsHeader. Kept as a field just to avoid creating a new one each time.
private final Rect mQsHeaderBound = new Rect();
private boolean mContinuousShadowUpdate;
- private boolean mContinuousBackgroundUpdate;
private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
updateViewShadows();
return true;
};
- private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
- updateBackground();
- return true;
- };
private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
float endY = view.getTranslationY() + view.getActualHeight();
float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -481,7 +445,6 @@
private boolean mHeadsUpAnimatingAway;
private int mStatusBarState;
private int mUpcomingStatusBarState;
- private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private final Runnable mReflingAndAnimateScroll = this::animateScroll;
private int mCornerRadius;
@@ -581,7 +544,6 @@
*/
private boolean mDismissUsingRowTranslationX = true;
private ExpandableNotificationRow mTopHeadsUpRow;
- private long mNumHeadsUp;
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
@@ -595,7 +557,7 @@
mSplitShadeStateController = splitShadeStateController;
updateSplitNotificationShade();
}
- private FeatureFlags mFeatureFlags;
+ private final FeatureFlags mFeatureFlags;
private final ExpandableView.OnHeightChangedListener mOnChildHeightChangedListener =
new ExpandableView.OnHeightChangedListener() {
@@ -667,8 +629,6 @@
mSections = mSectionsManager.createSectionsForBuckets();
mAmbientState = Dependency.get(AmbientState.class);
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
mSplitShadeMinContentHeight = res.getDimensionPixelSize(
@@ -680,16 +640,12 @@
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
mStateAnimator = new StackStateAnimator(context, this);
- mShouldDrawNotificationBackground =
- res.getBoolean(R.bool.config_drawNotificationBackground);
setOutlineProvider(mOutlineProvider);
// We could set this whenever we 'requestChildUpdate' much like the viewTreeObserver, but
// that adds a bunch of complexity, and drawing nothing isn't *that* expensive.
- boolean willDraw = SceneContainerFlag.isEnabled()
- || mShouldDrawNotificationBackground || mDebugLines;
+ boolean willDraw = SceneContainerFlag.isEnabled() || mDebugLines;
setWillNotDraw(!willDraw);
- mBackgroundPaint.setAntiAlias(true);
if (mDebugLines) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -812,9 +768,6 @@
}
void updateBgColor() {
- mBgColor = Utils.getColorAttr(mContext,
- com.android.internal.R.attr.materialColorSurfaceContainerHigh).getDefaultColor();
- updateBackgroundDimming();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
if (child instanceof ActivatableNotificationView activatableView) {
@@ -835,14 +788,6 @@
protected void onDraw(Canvas canvas) {
onJustBeforeDraw();
- if (mShouldDrawNotificationBackground
- && (mSections[0].getCurrentBounds().top
- < mSections[mSections.length - 1].getCurrentBounds().bottom
- || mAmbientState.isDozing())) {
- drawBackground(canvas);
- } else if (mInHeadsUpPinnedMode || mHeadsUpAnimatingAway) {
- drawHeadsUpBackground(canvas);
- }
if (mDebugLines) {
onDrawDebug(canvas);
@@ -930,150 +875,6 @@
return textY;
}
- private void drawBackground(Canvas canvas) {
- int lockScreenLeft = mSidePaddings;
- int lockScreenRight = getWidth() - mSidePaddings;
- int lockScreenTop = mSections[0].getCurrentBounds().top;
- int lockScreenBottom = mSections[mSections.length - 1].getCurrentBounds().bottom;
- int hiddenLeft = getWidth() / 2;
- int hiddenTop = mTopPadding;
-
- float yProgress = 1 - mInterpolatedHideAmount;
- float xProgress = mHideXInterpolator.getInterpolation(
- (1 - mLinearHideAmount) * mBackgroundXFactor);
-
- int left = (int) MathUtils.lerp(hiddenLeft, lockScreenLeft, xProgress);
- int right = (int) MathUtils.lerp(hiddenLeft, lockScreenRight, xProgress);
- int top = (int) MathUtils.lerp(hiddenTop, lockScreenTop, yProgress);
- int bottom = (int) MathUtils.lerp(hiddenTop, lockScreenBottom, yProgress);
- mBackgroundAnimationRect.set(
- left,
- top,
- right,
- bottom);
-
- int backgroundTopAnimationOffset = top - lockScreenTop;
- // TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
- boolean anySectionHasVisibleChild = false;
- for (NotificationSection section : mSections) {
- if (section.needsBackground()) {
- anySectionHasVisibleChild = true;
- break;
- }
- }
- boolean shouldDrawBackground;
- if (mKeyguardBypassEnabled && onKeyguard()) {
- shouldDrawBackground = isPulseExpanding();
- } else {
- shouldDrawBackground = !mAmbientState.isDozing() || anySectionHasVisibleChild;
- }
- if (shouldDrawBackground) {
- drawBackgroundRects(canvas, left, right, top, backgroundTopAnimationOffset);
- }
-
- updateClipping();
- }
-
- /**
- * Draws round rects for each background section.
- * <p>
- * We want to draw a round rect for each background section as defined by {@link #mSections}.
- * However, if two sections are directly adjacent with no gap between them (e.g. on the
- * lockscreen where the shelf can appear directly below the high priority section, or while
- * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
- * section), we don't want to round the adjacent corners.
- * <p>
- * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
- * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
- * This method tracks the top of each rect we need to draw, then iterates through the visible
- * sections. If a section is not adjacent to the previous section, we draw the previous rect
- * behind the sections we've accumulated up to that point, then start a new rect at the top of
- * the current section. When we're done iterating we will always have one rect left to draw.
- */
- private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
- int animationYOffset) {
- int backgroundRectTop = top;
- int lastSectionBottom =
- mSections[0].getCurrentBounds().bottom + animationYOffset;
- int currentLeft = left;
- int currentRight = right;
- boolean first = true;
- for (NotificationSection section : mSections) {
- if (!section.needsBackground()) {
- continue;
- }
- int sectionTop = section.getCurrentBounds().top + animationYOffset;
- int ownLeft = Math.min(Math.max(left, section.getCurrentBounds().left), right);
- int ownRight = Math.max(Math.min(right, section.getCurrentBounds().right), ownLeft);
- // If sections are directly adjacent to each other, we don't want to draw them
- // as separate roundrects, as the rounded corners right next to each other look
- // bad.
- if (sectionTop - lastSectionBottom > DISTANCE_BETWEEN_ADJACENT_SECTIONS_PX
- || ((currentLeft != ownLeft || currentRight != ownRight) && !first)) {
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- backgroundRectTop = sectionTop;
- }
- currentLeft = ownLeft;
- currentRight = ownRight;
- lastSectionBottom =
- section.getCurrentBounds().bottom + animationYOffset;
- first = false;
- }
- canvas.drawRoundRect(currentLeft,
- backgroundRectTop,
- currentRight,
- lastSectionBottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
-
- private void drawHeadsUpBackground(Canvas canvas) {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
-
- float top = getHeight();
- float bottom = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE
- && child instanceof ExpandableNotificationRow row) {
- if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
- && row.getProvider().shouldShowGutsOnSnapOpen()) {
- top = Math.min(top, row.getTranslationY());
- bottom = Math.max(bottom, row.getTranslationY() + row.getActualHeight());
- }
- }
- }
-
- if (top < bottom) {
- canvas.drawRoundRect(
- left, top, right, bottom,
- mCornerRadius, mCornerRadius, mBackgroundPaint);
- }
- }
-
- void updateBackgroundDimming() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
- // Interpolate between semi-transparent notification panel background color
- // and white AOD separator.
- float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
- mLinearHideAmount);
- int color = ColorUtils.blendARGB(mBgColor, Color.WHITE, colorInterpolation);
-
- if (mCachedBackgroundColor != color) {
- mCachedBackgroundColor = color;
- mBackgroundPaint.setColor(color);
- invalidate();
- }
- }
-
private void reinitView() {
initView(getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
}
@@ -1359,9 +1160,6 @@
private void onPreDrawDuringAnimation() {
mShelf.updateAppearance();
- if (!mNeedsAnimation && !mChildrenUpdateRequested) {
- updateBackground();
- }
}
private void updateScrollStateForAddedChildren() {
@@ -2565,125 +2363,6 @@
}
}
- private void updateBackground() {
- // No need to update the background color if it's not being drawn.
- if (!mShouldDrawNotificationBackground) {
- return;
- }
-
- updateBackgroundBounds();
- if (didSectionBoundsChange()) {
- boolean animate = mAnimateNextSectionBoundsChange || mAnimateNextBackgroundTop
- || mAnimateNextBackgroundBottom || areSectionBoundsAnimating();
- if (!isExpanded()) {
- abortBackgroundAnimators();
- animate = false;
- }
- if (animate) {
- startBackgroundAnimation();
- } else {
- for (NotificationSection section : mSections) {
- section.resetCurrentBounds();
- }
- invalidate();
- }
- } else {
- abortBackgroundAnimators();
- }
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
- }
-
- private void abortBackgroundAnimators() {
- for (NotificationSection section : mSections) {
- section.cancelAnimators();
- }
- }
-
- private boolean didSectionBoundsChange() {
- for (NotificationSection section : mSections) {
- if (section.didBoundsChange()) {
- return true;
- }
- }
- return false;
- }
-
- private boolean areSectionBoundsAnimating() {
- for (NotificationSection section : mSections) {
- if (section.areBoundsAnimating()) {
- return true;
- }
- }
- return false;
- }
-
- private void startBackgroundAnimation() {
- // TODO(kprevas): do we still need separate fields for top/bottom?
- // or can each section manage its own animation state?
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- NotificationSection lastVisibleSection = getLastVisibleSection();
- for (NotificationSection section : mSections) {
- section.startBackgroundAnimation(
- section == firstVisibleSection
- ? mAnimateNextBackgroundTop
- : mAnimateNextSectionBoundsChange,
- section == lastVisibleSection
- ? mAnimateNextBackgroundBottom
- : mAnimateNextSectionBoundsChange);
- }
- }
-
- /**
- * Update the background bounds to the new desired bounds
- */
- private void updateBackgroundBounds() {
- int left = mSidePaddings;
- int right = getWidth() - mSidePaddings;
- for (NotificationSection section : mSections) {
- section.getBounds().left = left;
- section.getBounds().right = right;
- }
-
- if (!mIsExpanded) {
- for (NotificationSection section : mSections) {
- section.getBounds().top = 0;
- section.getBounds().bottom = 0;
- }
- return;
- }
- int minTopPosition;
- NotificationSection lastSection = getLastVisibleSection();
- boolean onKeyguard = mStatusBarState == StatusBarState.KEYGUARD;
- if (!onKeyguard) {
- minTopPosition = (int) (mTopPadding + mStackTranslation);
- } else if (lastSection == null) {
- minTopPosition = mTopPadding;
- } else {
- // The first sections could be empty while there could still be elements in later
- // sections. The position of these first few sections is determined by the position of
- // the first visible section.
- NotificationSection firstVisibleSection = getFirstVisibleSection();
- firstVisibleSection.updateBounds(0 /* minTopPosition*/, 0 /* minBottomPosition */,
- false /* shiftPulsingWithFirst */);
- minTopPosition = firstVisibleSection.getBounds().top;
- }
- boolean shiftPulsingWithFirst = mNumHeadsUp <= 1
- && (mAmbientState.isDozing() || (mKeyguardBypassEnabled && onKeyguard));
- for (NotificationSection section : mSections) {
- int minBottomPosition = minTopPosition;
- if (section == lastSection) {
- // We need to make sure the section goes all the way to the shelf
- minBottomPosition = (int) (ViewState.getFinalTranslationY(mShelf)
- + mShelf.getIntrinsicHeight());
- }
- minTopPosition = section.updateBounds(minTopPosition, minBottomPosition,
- shiftPulsingWithFirst);
- shiftPulsingWithFirst = false;
- }
- }
-
private NotificationSection getFirstVisibleSection() {
for (NotificationSection section : mSections) {
if (section.getFirstVisibleChild() != null) {
@@ -3184,13 +2863,7 @@
mSections, getChildrenWithBackground());
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != previousFirstChild;
- mAnimateNextBackgroundBottom = lastChild != previousLastChild || mAnimateBottomOnLayout;
- mAnimateNextSectionBoundsChange = sectionViewsChanged;
} else {
- mAnimateNextBackgroundTop = false;
- mAnimateNextBackgroundBottom = false;
- mAnimateNextSectionBoundsChange = false;
}
mAmbientState.setLastVisibleBackgroundChild(lastChild);
mAnimateBottomOnLayout = false;
@@ -3344,7 +3017,6 @@
setAnimationRunning(true);
mStateAnimator.startAnimationForEvents(mAnimationEvents, mGoToFullShadeDelay);
mAnimationEvents.clear();
- updateBackground();
updateViewShadows();
} else {
applyCurrentState();
@@ -3359,7 +3031,6 @@
generatePositionChangeEvents();
generateTopPaddingEvent();
generateActivateEvent();
- generateDimmedEvent();
generateHideSensitiveEvent();
generateGoToFullShadeEvent();
generateViewResizeEvent();
@@ -3577,14 +3248,6 @@
mEverythingNeedsAnimation = false;
}
- private void generateDimmedEvent() {
- if (mDimmedNeedsAnimation) {
- mAnimationEvents.add(
- new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
- }
- mDimmedNeedsAnimation = false;
- }
-
private void generateHideSensitiveEvent() {
if (mHideSensitiveNeedsAnimation) {
mAnimationEvents.add(
@@ -3645,7 +3308,11 @@
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
- if (!mSendingTouchesToSceneFramework) {
+ int action = ev.getActionMasked();
+ boolean isUpOrCancel = action == ACTION_UP || action == ACTION_CANCEL;
+ if (mSendingTouchesToSceneFramework) {
+ mController.sendTouchToSceneFramework(ev);
+ } else if (!isUpOrCancel) {
// if this is the first touch being sent to the scene framework,
// convert it into a synthetic DOWN event.
mSendingTouchesToSceneFramework = true;
@@ -3653,14 +3320,9 @@
downEvent.setAction(MotionEvent.ACTION_DOWN);
mController.sendTouchToSceneFramework(downEvent);
downEvent.recycle();
- } else {
- mController.sendTouchToSceneFramework(ev);
}
- if (
- ev.getActionMasked() == MotionEvent.ACTION_UP
- || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
- ) {
+ if (isUpOrCancel) {
setIsBeingDragged(false);
}
return false;
@@ -3817,7 +3479,7 @@
}
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
@@ -3854,7 +3516,7 @@
}
break;
- case MotionEvent.ACTION_CANCEL:
+ case ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
getScrollRange())) {
@@ -3963,7 +3625,7 @@
mTouchIsClick = false;
}
break;
- case MotionEvent.ACTION_UP:
+ case ACTION_UP:
if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
debugShadeLog("handleEmptySpaceClick: touch event propagated further");
@@ -4104,8 +3766,8 @@
break;
}
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
+ case ACTION_CANCEL:
+ case ACTION_UP:
/* Release the drag */
setIsBeingDragged(false);
mActivePointerId = INVALID_POINTER;
@@ -4486,48 +4148,6 @@
mAnimationFinishedRunnables.clear();
}
- /**
- * See {@link AmbientState#setDimmed}.
- */
- void setDimmed(boolean dimmed, boolean animate) {
- dimmed &= onKeyguard();
- mAmbientState.setDimmed(dimmed);
- if (animate && mAnimationsEnabled) {
- mDimmedNeedsAnimation = true;
- mNeedsAnimation = true;
- animateDimmed(dimmed);
- } else {
- setDimAmount(dimmed ? 1.0f : 0.0f);
- }
- requestChildrenUpdate();
- }
-
- @VisibleForTesting
- boolean isDimmed() {
- return mAmbientState.isDimmed();
- }
-
- private void setDimAmount(float dimAmount) {
- mDimAmount = dimAmount;
- updateBackgroundDimming();
- }
-
- private void animateDimmed(boolean dimmed) {
- if (mDimAnimator != null) {
- mDimAnimator.cancel();
- }
- float target = dimmed ? 1.0f : 0.0f;
- if (target == mDimAmount) {
- return;
- }
- mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
- mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
- mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mDimAnimator.addListener(mDimEndListener);
- mDimAnimator.addUpdateListener(mDimUpdateListener);
- mDimAnimator.start();
- }
-
void updateSensitiveness(boolean animate, boolean hideSensitive) {
if (hideSensitive != mAmbientState.isHideSensitive()) {
int childCount = getChildCount();
@@ -4564,7 +4184,6 @@
runAnimationFinishedRunnables();
setAnimationRunning(false);
- updateBackground();
updateViewShadows();
}
@@ -4714,7 +4333,6 @@
invalidateOutline();
}
updateAlgorithmHeightAndPadding();
- updateBackgroundDimming();
requestChildrenUpdate();
updateOwnTranslationZ();
}
@@ -4747,21 +4365,6 @@
}
}
- private int getNotGoneIndex(View child) {
- int count = getChildCount();
- int notGoneIndex = 0;
- for (int i = 0; i < count; i++) {
- View v = getChildAt(i);
- if (child == v) {
- return notGoneIndex;
- }
- if (v.getVisibility() != View.GONE) {
- notGoneIndex++;
- }
- }
- return -1;
- }
-
/**
* Returns whether or not a History button is shown in the footer. If there is no footer, then
* this will return false.
@@ -5266,13 +4869,10 @@
void onStatePostChange(boolean fromShadeLocked) {
boolean onKeyguard = onKeyguard();
- mAmbientState.setDimmed(onKeyguard);
-
if (mHeadsUpAppearanceController != null) {
mHeadsUpAppearanceController.onStateChanged();
}
- setDimmed(onKeyguard, fromShadeLocked);
setExpandingEnabled(!onKeyguard);
if (!FooterViewRefactor.isEnabled()) {
updateFooter();
@@ -5676,7 +5276,6 @@
*/
public void setDozeAmount(float dozeAmount) {
mAmbientState.setDozeAmount(dozeAmount);
- updateContinuousBackgroundDrawing();
updateStackPosition();
requestChildrenUpdate();
}
@@ -5711,7 +5310,6 @@
view.setTranslationY(wakeUplocation);
}
}
- mDimmedNeedsAnimation = true;
}
void setAnimateBottomOnLayout(boolean animateBottomOnLayout) {
@@ -5763,7 +5361,6 @@
updateFirstAndLastBackgroundViews();
requestDisallowInterceptTouchEvent(true);
updateContinuousShadowDrawing();
- updateContinuousBackgroundDrawing();
requestChildrenUpdate();
}
@@ -5786,7 +5383,6 @@
* @param numHeadsUp the number of active alerting notifications.
*/
public void setNumHeadsUp(long numHeadsUp) {
- mNumHeadsUp = numHeadsUp;
mAmbientState.setHasHeadsUpEntries(numHeadsUp > 0);
}
@@ -6160,19 +5756,6 @@
mSpeedBumpIndexDirty = true;
}
- void updateContinuousBackgroundDrawing() {
- boolean continuousBackground = !mAmbientState.isFullyAwake()
- && mSwipeHelper.isSwiping();
- if (continuousBackground != mContinuousBackgroundUpdate) {
- mContinuousBackgroundUpdate = continuousBackground;
- if (continuousBackground) {
- getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
- } else {
- getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
- }
- }
- }
-
private void resetAllSwipeState() {
Trace.beginSection("NSSL.resetAllSwipeState()");
mSwipeHelper.resetTouchState();
@@ -6259,7 +5842,6 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ(),
// ANIMATION_TYPE_ACTIVATED_CHILD
@@ -6267,8 +5849,7 @@
.animateZ(),
// ANIMATION_TYPE_DIMMED
- new AnimationFilter()
- .animateDimmed(),
+ new AnimationFilter(),
// ANIMATION_TYPE_CHANGE_POSITION
new AnimationFilter()
@@ -6283,7 +5864,6 @@
.animateHeight()
.animateTopInset()
.animateY()
- .animateDimmed()
.animateZ()
.hasDelays(),
@@ -6339,7 +5919,6 @@
// ANIMATION_TYPE_EVERYTHING
new AnimationFilter()
.animateAlpha()
- .animateDimmed()
.animateHideSensitive()
.animateHeight()
.animateTopInset()
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 7c13877..3bdd0e9 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
@@ -875,8 +875,6 @@
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
- mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
-
mLockscreenShadeTransitionController.setStackScroller(this);
mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
@@ -1743,13 +1741,6 @@
}
/**
- * Set the dimmed state for all of the notification views.
- */
- public void setDimmed(boolean dimmed, boolean animate) {
- mView.setDimmed(dimmed, animate);
- }
-
- /**
* @return the inset during the full shade transition, that needs to be added to the position
* of the quick settings edge. This is relevant for media, that is transitioning
* from the keyguard host to the quick settings one.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1ef9a8f..9b1952b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -369,13 +369,11 @@
/** Updates the dimmed and hiding sensitive states of the children. */
private void updateDimmedAndHideSensitive(AmbientState ambientState,
StackScrollAlgorithmState algorithmState) {
- boolean dimmed = ambientState.isDimmed();
boolean hideSensitive = ambientState.isHideSensitive();
int childCount = algorithmState.visibleChildren.size();
for (int i = 0; i < childCount; i++) {
ExpandableView child = algorithmState.visibleChildren.get(i);
ExpandableViewState childViewState = child.getViewState();
- childViewState.dimmed = dimmed;
childViewState.hideSensitive = hideSensitive;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index f523793..3a9cdd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -42,8 +42,10 @@
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.GoneToAodTransitionViewModel
@@ -99,7 +101,9 @@
private val alternateBouncerToGoneTransitionViewModel:
AlternateBouncerToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
+ private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
+ private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val glanceableHubToLockscreenTransitionViewModel:
GlanceableHubToLockscreenTransitionViewModel,
@@ -155,8 +159,8 @@
.distinctUntilChanged()
.dumpWhileCollecting("isShadeLocked")
- private val shadeCollapseFadeInComplete = MutableStateFlow(false)
- .dumpValue("shadeCollapseFadeInComplete")
+ private val shadeCollapseFadeInComplete =
+ MutableStateFlow(false).dumpValue("shadeCollapseFadeInComplete")
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
interactor.configurationBasedDimensions
@@ -187,9 +191,8 @@
statesForConstrainedNotifications.contains(it)
},
keyguardTransitionInteractor
- .transitionValue(LOCKSCREEN)
- .onStart { emit(0f) }
- .map { it > 0 }
+ .isInTransitionWhere { from, to -> from == LOCKSCREEN || to == LOCKSCREEN }
+ .onStart { emit(false) }
) { constrainedNotificationState, transitioningToOrFromLockscreen ->
constrainedNotificationState || transitioningToOrFromLockscreen
}
@@ -362,16 +365,16 @@
private val alphaWhenGoneAndShadeState: Flow<Float> =
combineTransform(
- keyguardTransitionInteractor.transitions
- .map { step -> step.to == GONE && step.transitionState == FINISHED }
- .distinctUntilChanged(),
- keyguardInteractor.statusBarState,
- ) { isGoneTransitionFinished, statusBarState ->
- if (isGoneTransitionFinished && statusBarState == SHADE) {
- emit(1f)
+ keyguardTransitionInteractor.transitions
+ .map { step -> step.to == GONE && step.transitionState == FINISHED }
+ .distinctUntilChanged(),
+ keyguardInteractor.statusBarState,
+ ) { isGoneTransitionFinished, statusBarState ->
+ if (isGoneTransitionFinished && statusBarState == SHADE) {
+ emit(1f)
+ }
}
- }
- .dumpWhileCollecting("alphaWhenGoneAndShadeState")
+ .dumpWhileCollecting("alphaWhenGoneAndShadeState")
fun expansionAlpha(viewState: ViewStateAccessor): Flow<Float> {
// All transition view models are mututally exclusive, and safe to merge
@@ -379,7 +382,9 @@
merge(
alternateBouncerToGoneTransitionViewModel.lockscreenAlpha,
aodToLockscreenTransitionViewModel.notificationAlpha,
+ aodToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dozingToLockscreenTransitionViewModel.lockscreenAlpha,
+ dozingToOccludedTransitionViewModel.lockscreenAlpha(viewState),
dreamingToLockscreenTransitionViewModel.lockscreenAlpha,
goneToAodTransitionViewModel.notificationAlpha,
goneToDreamingTransitionViewModel.lockscreenAlpha,
@@ -433,30 +438,35 @@
* alpha sources.
*/
val glanceableHubAlpha: Flow<Float> =
- isOnGlanceableHubWithoutShade.flatMapLatest { isOnGlanceableHubWithoutShade ->
- combineTransform(
- lockscreenToGlanceableHubRunning,
- glanceableHubToLockscreenRunning,
- merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
- glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
- )
- ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
- if (isOnGlanceableHubWithoutShade) {
- // Notifications should not be visible on the glanceable hub.
- // TODO(b/321075734): implement a way to actually set the notifications to gone
- // while on the hub instead of just adjusting alpha
- emit(0f)
- } else if (lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning) {
- emit(alpha)
- } else {
- // Not on the hub and no transitions running, return full visibility so we don't
- // block the notifications from showing.
- emit(1f)
+ isOnGlanceableHubWithoutShade
+ .flatMapLatest { isOnGlanceableHubWithoutShade ->
+ combineTransform(
+ lockscreenToGlanceableHubRunning,
+ glanceableHubToLockscreenRunning,
+ merge(
+ lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
+ glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ )
+ ) { lockscreenToGlanceableHubRunning, glanceableHubToLockscreenRunning, alpha ->
+ if (isOnGlanceableHubWithoutShade) {
+ // Notifications should not be visible on the glanceable hub.
+ // TODO(b/321075734): implement a way to actually set the notifications to
+ // gone
+ // while on the hub instead of just adjusting alpha
+ emit(0f)
+ } else if (
+ lockscreenToGlanceableHubRunning || glanceableHubToLockscreenRunning
+ ) {
+ emit(alpha)
+ } else {
+ // Not on the hub and no transitions running, return full visibility so we
+ // don't
+ // block the notifications from showing.
+ emit(1f)
+ }
}
}
- }
- .dumpWhileCollecting("glanceableHubAlpha")
+ .dumpWhileCollecting("glanceableHubAlpha")
/**
* Under certain scenarios, such as swiping up on the lockscreen, the container will need to be
@@ -464,20 +474,23 @@
*/
fun translationY(params: BurnInParameters): Flow<Float> {
return combine(
- aodBurnInViewModel.translationY(params).onStart { emit(0f) },
- isOnLockscreenWithoutShade,
- merge(
- keyguardInteractor.keyguardTranslationY,
- occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
- )
- ) { burnInY, isOnLockscreenWithoutShade, translationY ->
- if (isOnLockscreenWithoutShade) {
- burnInY + translationY
- } else {
- 0f
+ aodBurnInViewModel
+ .movement(params)
+ .map { it.translationY.toFloat() }
+ .onStart { emit(0f) },
+ isOnLockscreenWithoutShade,
+ merge(
+ keyguardInteractor.keyguardTranslationY,
+ occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+ )
+ ) { burnInY, isOnLockscreenWithoutShade, translationY ->
+ if (isOnLockscreenWithoutShade) {
+ burnInY + translationY
+ } else {
+ 0f
+ }
}
- }
- .dumpWhileCollecting("translationY")
+ .dumpWhileCollecting("translationY")
}
/**
@@ -486,10 +499,10 @@
*/
val translationX: Flow<Float> =
merge(
- lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
- glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
- )
- .dumpWhileCollecting("translationX")
+ lockscreenToGlanceableHubTransitionViewModel.notificationTranslationX,
+ glanceableHubToLockscreenTransitionViewModel.notificationTranslationX,
+ )
+ .dumpWhileCollecting("translationX")
/**
* When on keyguard, there is limited space to display notifications so calculate how many could
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 560d5ba..48d3157 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -315,8 +315,7 @@
mMetricsLogger.count("panel_open", 1);
} else if (!mQsController.getExpanded()
&& !mShadeViewController.isExpandingOrCollapsing()) {
- mQsController.flingQs(0 /* velocity */,
- ShadeViewController.FLING_EXPAND);
+ mShadeController.animateExpandQs();
mMetricsLogger.count("panel_open_qs", 1);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d2e36b8..088f894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -207,8 +207,6 @@
private ScrimView mNotificationsScrim;
private ScrimView mScrimBehind;
- private Runnable mScrimBehindChangeRunnable;
-
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
@@ -415,11 +413,6 @@
behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
mNotificationsScrim.enableRoundedCorners(true);
- if (mScrimBehindChangeRunnable != null) {
- mScrimBehind.setChangeRunnable(mScrimBehindChangeRunnable, mMainExecutor);
- mScrimBehindChangeRunnable = null;
- }
-
final ScrimState[] states = ScrimState.values();
for (int i = 0; i < states.length; i++) {
states[i].init(mScrimInFront, mScrimBehind, mDozeParameters, mDockManager);
@@ -1542,16 +1535,6 @@
mScrimBehind.postOnAnimationDelayed(callback, 32 /* delayMillis */);
}
- public void setScrimBehindChangeRunnable(Runnable changeRunnable) {
- // TODO: remove this. This is necessary because of an order-of-operations limitation.
- // The fix is to move more of these class into @SysUISingleton.
- if (mScrimBehind == null) {
- mScrimBehindChangeRunnable = changeRunnable;
- } else {
- mScrimBehind.setChangeRunnable(changeRunnable, mMainExecutor);
- }
- }
-
private void updateThemeColors() {
if (mScrimBehind == null) return;
int background = Utils.getColorAttr(mScrimBehind.getContext(),
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 d1055c7..ba89d4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -81,6 +81,9 @@
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
@@ -92,6 +95,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.unfold.FoldAodAnimationController;
@@ -157,6 +161,7 @@
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final BouncerView mPrimaryBouncerView;
private final Lazy<ShadeController> mShadeController;
+ private final Lazy<SceneInteractor> mSceneInteractorLazy;
// Local cache of expansion events, to avoid duplicates
private float mFraction = -1f;
@@ -347,8 +352,8 @@
private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
private Lazy<KeyguardSurfaceBehindInteractor> mSurfaceBehindInteractor;
private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
-
private final JavaAdapter mJavaAdapter;
+ private StatusBarKeyguardViewManagerInteractor mStatusBarKeyguardViewManagerInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -381,7 +386,9 @@
Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy,
SelectedUserInteractor selectedUserInteractor,
Lazy<KeyguardSurfaceBehindInteractor> surfaceBehindInteractor,
- JavaAdapter javaAdapter
+ JavaAdapter javaAdapter,
+ Lazy<SceneInteractor> sceneInteractorLazy,
+ StatusBarKeyguardViewManagerInteractor statusBarKeyguardViewManagerInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -415,6 +422,8 @@
mSelectedUserInteractor = selectedUserInteractor;
mSurfaceBehindInteractor = surfaceBehindInteractor;
mJavaAdapter = javaAdapter;
+ mSceneInteractorLazy = sceneInteractorLazy;
+ mStatusBarKeyguardViewManagerInteractor = statusBarKeyguardViewManagerInteractor;
}
KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -497,6 +506,11 @@
lockscreenVis || animatingSurface
),
this::consumeShowStatusBarKeyguardView);
+
+ mJavaAdapter.alwaysCollectFlow(
+ mStatusBarKeyguardViewManagerInteractor.getKeyguardViewOcclusionState(),
+ (occlusionState) -> setOccluded(
+ occlusionState.getOccluded(), occlusionState.getAnimate()));
}
}
@@ -633,8 +647,11 @@
public void show(Bundle options) {
Trace.beginSection("StatusBarKeyguardViewManager#show");
mNotificationShadeWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(true,
- mKeyguardStateController.isOccluded());
+ if (SceneContainerFlag.isEnabled()) {
+ mSceneInteractorLazy.get().changeScene(
+ Scenes.Lockscreen, "StatusBarKeyguardViewManager.show");
+ }
+ mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
@@ -1444,6 +1461,10 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (KeyguardWmStateRefactor.isEnabled()) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
index 71e25e9..541ac48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialog.kt
@@ -23,6 +23,9 @@
import android.os.Bundle
import android.view.Gravity
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.WindowInsets
+import android.view.WindowInsets.Type.InsetsType
+import android.view.WindowInsetsAnimation
import android.view.WindowManager.LayoutParams.MATCH_PARENT
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
@@ -70,16 +73,43 @@
override fun onStart() {
super.onStart()
configurationController?.addCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(insetsAnimationCallback)
}
override fun onStop() {
super.onStop()
configurationController?.removeCallback(onConfigChanged)
+ window?.decorView?.setWindowInsetsAnimationCallback(null)
}
+ /** Called after any insets change. */
+ open fun onInsetsChanged(@InsetsType changedTypes: Int, insets: WindowInsets) {}
+
/** Can be overridden by subclasses to receive config changed events. */
open fun onConfigurationChanged() {}
+ private val insetsAnimationCallback =
+ object : WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private var lastInsets: WindowInsets? = null
+
+ override fun onEnd(animation: WindowInsetsAnimation) {
+ lastInsets?.let { onInsetsChanged(animation.typeMask, it) }
+ }
+
+ override fun onProgress(
+ insets: WindowInsets,
+ animations: MutableList<WindowInsetsAnimation>,
+ ): WindowInsets {
+ lastInsets = insets
+ onInsetsChanged(changedTypes = allAnimationMasks(animations), insets)
+ return insets
+ }
+
+ private fun allAnimationMasks(animations: List<WindowInsetsAnimation>): Int =
+ animations.fold(0) { acc: Int, it -> acc or it.typeMask }
+ }
+
private val onConfigChanged =
object : ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index f12a09b..82d9fc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,7 +93,7 @@
/**
* @deprecated Don't subclass SystemUIDialog. Please subclass {@link Delegate} and pass it to
- * {@link Factory#create(DialogDelegate)} to create a custom dialog.
+ * {@link Factory#create(Delegate)} to create a custom dialog.
*/
@Deprecated
public SystemUIDialog(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 44c684c..b5efc44 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -379,7 +379,9 @@
+ " was received. Deferring... Managed profile? " + isManagedProfile);
return;
}
- if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()
+ && isPrivateProfile(newUserHandle)) {
mDeferredThemeEvaluation = true;
Log.i(TAG, "Deferring theme for private profile till user setup is complete");
return;
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
index 0128eb7..80ccd64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -22,6 +22,24 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onStart
+fun BatteryController.isDevicePluggedIn(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(pluggedIn)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isPluggedIn) }
+}
+
fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
return conflatedCallbackFlow {
val batteryCallback =
@@ -35,3 +53,35 @@
}
.onStart { emit(isPowerSave) }
}
+
+fun BatteryController.getBatteryLevel(): Flow<Int> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onBatteryLevelChanged(
+ level: Int,
+ pluggedIn: Boolean,
+ charging: Boolean
+ ) {
+ trySend(level)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(0) }
+}
+
+fun BatteryController.isExtremePowerSaverEnabled(): Flow<Boolean> {
+ return conflatedCallbackFlow {
+ val batteryCallback =
+ object : BatteryController.BatteryStateChangeCallback {
+ override fun onExtremeBatterySaverChanged(isExtreme: Boolean) {
+ trySend(isExtreme)
+ }
+ }
+ addCallback(batteryCallback)
+ awaitClose { removeCallback(batteryCallback) }
+ }
+ .onStart { emit(isExtremeSaverOn) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
index 0d6c0f5..10cf082 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java
@@ -25,8 +25,11 @@
import android.os.UserHandle;
import android.provider.Settings;
+import com.android.app.tracing.TraceUtils;
import com.android.systemui.settings.UserTracker;
+import kotlin.Unit;
+
/**
* Used to interact with per-user Settings.Secure and Settings.System settings (but not
* Settings.Global, since those do not vary per-user)
@@ -123,8 +126,16 @@
default void registerContentObserverForUser(
Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver,
int userHandle) {
- getContentResolver().registerContentObserver(
- uri, notifyForDescendants, settingsObserver, getRealUserHandle(userHandle));
+ TraceUtils.trace(
+ () -> {
+ // The limit for trace tags length is 127 chars, which leaves us 90 for Uri.
+ return "USP#registerObserver#[" + uri.toString() + "]";
+ }, () -> {
+ getContentResolver().registerContentObserver(
+ uri, notifyForDescendants, settingsObserver,
+ getRealUserHandle(userHandle));
+ return Unit.INSTANCE;
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
index db300eb..2157fafa 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/ClientTrackingWakeLock.kt
@@ -18,6 +18,7 @@
import android.os.PowerManager
import android.util.Log
+import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
@@ -36,7 +37,11 @@
override fun acquire(why: String) {
val count = activeClients.computeIfAbsent(why) { _ -> AtomicInteger(0) }.incrementAndGet()
logger?.logAcquire(pmWakeLock, why, count)
- pmWakeLock.acquire(maxTimeout)
+ if (maxTimeout == NO_TIMEOUT) {
+ pmWakeLock.acquire()
+ } else {
+ pmWakeLock.acquire(maxTimeout)
+ }
}
override fun release(why: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
index 707751a..f763ee4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
+++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLock.java
@@ -130,7 +130,11 @@
if (logger != null) {
logger.logAcquire(inner, why, count);
}
- inner.acquire(maxTimeout);
+ if (maxTimeout == Builder.NO_TIMEOUT) {
+ inner.acquire();
+ } else {
+ inner.acquire(maxTimeout);
+ }
}
/** @see PowerManager.WakeLock#release() */
@@ -169,6 +173,7 @@
* An injectable Builder that wraps {@link #createPartial(Context, String, long)}.
*/
class Builder {
+ public static final long NO_TIMEOUT = -1;
private final Context mContext;
private final WakeLockLogger mLogger;
private String mTag;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 4045630..deec215 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -120,7 +120,7 @@
import com.android.systemui.haptics.slider.HapticSliderViewBinder;
import com.android.systemui.haptics.slider.SeekableSliderHapticPlugin;
import com.android.systemui.haptics.slider.SliderHapticFeedbackConfig;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
@@ -265,7 +265,7 @@
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ConfigurationController mConfigurationController;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private final MediaOutputDialogManager mMediaOutputDialogManager;
private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
private final VolumePanelNavigationInteractor mVolumePanelNavigationInteractor;
private final VolumeNavigator mVolumeNavigator;
@@ -316,7 +316,7 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -340,7 +340,7 @@
mAccessibilityMgr = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
mConfigurationController = configurationController;
- mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mMediaOutputDialogManager = mediaOutputDialogManager;
mCsdWarningDialogFactory = csdWarningDialogFactory;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
@@ -1199,7 +1199,7 @@
mSettingsIcon.setOnClickListener(v -> {
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
- mMediaOutputDialogFactory.dismiss();
+ mMediaOutputDialogManager.dismiss();
mVolumeNavigator.openVolumePanel(
mVolumePanelNavigationInteractor.getVolumePanelRoute());
});
@@ -2082,6 +2082,9 @@
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
+ // The animator can't keep up with the volume changes so haptics need to be
+ // triggered here. This happens when the volume keys are continuously pressed.
+ row.deliverOnProgressChangedHaptics(false, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
@@ -2486,10 +2489,10 @@
if (getActiveRow().equals(mRow)
&& mRow.slider.getVisibility() == VISIBLE
&& mRow.mHapticPlugin != null) {
- mRow.mHapticPlugin.onProgressChanged(seekBar, progress, fromUser);
- if (!fromUser) {
- // Consider a change from program as the volume key being continuously pressed
- mRow.mHapticPlugin.onKeyDown();
+ if (fromUser || mRow.animTargetProgress == progress) {
+ // Deliver user-generated slider changes immediately, or when the animation
+ // completes
+ mRow.deliverOnProgressChangedHaptics(fromUser, progress);
}
}
if (D.BUG) Log.d(TAG, AudioSystem.streamToString(mRow.stream)
@@ -2571,11 +2574,11 @@
/* progressInterpolatorFactor= */ 1f,
/* progressBasedDragMinScale= */ 0f,
/* progressBasedDragMaxScale= */ 0.2f,
- /* additionalVelocityMaxBump= */ 0.15f,
+ /* additionalVelocityMaxBump= */ 0.25f,
/* deltaMillisForDragInterval= */ 0f,
- /* deltaProgressForDragThreshold= */ 0.015f,
- /* numberOfLowTicks= */ 5,
- /* maxVelocityToScale= */ 300f,
+ /* deltaProgressForDragThreshold= */ 0.05f,
+ /* numberOfLowTicks= */ 4,
+ /* maxVelocityToScale= */ 200,
/* velocityAxis= */ MotionEvent.AXIS_Y,
/* upperBookendScale= */ 1f,
/* lowerBookendScale= */ 0.05f,
@@ -2642,6 +2645,14 @@
void removeHaptics() {
slider.setOnTouchListener(null);
}
+
+ void deliverOnProgressChangedHaptics(boolean fromUser, int progress) {
+ mHapticPlugin.onProgressChanged(slider, progress, fromUser);
+ if (!fromUser) {
+ // Consider a change from program as the volume key being continuously pressed
+ mHapticPlugin.onKeyDown();
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
index 593b90a..4ba7cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/SpatializerModule.kt
@@ -21,12 +21,10 @@
import com.android.settingslib.media.data.repository.SpatializerRepository
import com.android.settingslib.media.data.repository.SpatializerRepositoryImpl
import com.android.settingslib.media.domain.interactor.SpatializerInteractor
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
/** Spatializer module. */
@Module
@@ -42,9 +40,8 @@
@Provides
fun provdieSpatializerRepository(
spatializer: Spatializer,
- @Application scope: CoroutineScope,
@Background backgroundContext: CoroutineContext,
- ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, scope, backgroundContext)
+ ): SpatializerRepository = SpatializerRepositoryImpl(spatializer, backgroundContext)
@Provides
fun provideSpatializerInetractor(repository: SpatializerRepository): SpatializerInteractor =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 64a5644..1f87ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -24,7 +24,7 @@
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.CoreStartable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.statusbar.VibratorHelper;
@@ -100,7 +100,7 @@
AccessibilityManagerWrapper accessibilityManagerWrapper,
DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,
- MediaOutputDialogFactory mediaOutputDialogFactory,
+ MediaOutputDialogManager mediaOutputDialogManager,
InteractionJankMonitor interactionJankMonitor,
VolumePanelNavigationInteractor volumePanelNavigationInteractor,
VolumeNavigator volumeNavigator,
@@ -116,7 +116,7 @@
accessibilityManagerWrapper,
deviceProvisionedController,
configurationController,
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
interactionJankMonitor,
volumePanelNavigationInteractor,
volumeNavigator,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
index 8ab563a..6c47aec 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/button/ui/viewmodel/ToggleButtonViewModel.kt
@@ -23,3 +23,6 @@
val icon: Icon,
val label: CharSequence,
)
+
+fun ToggleButtonViewModel.toButtonViewModel(): ButtonViewModel =
+ ButtonViewModel(icon = icon, label = label)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 170b32c..cb16abe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -16,14 +16,11 @@
package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor
-import android.content.Intent
-import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -33,29 +30,22 @@
class MediaOutputActionsInteractor
@Inject
constructor(
- private val mediaOutputDialogFactory: MediaOutputDialogFactory,
- private val activityStarter: ActivityStarter,
+ private val mediaOutputDialogManager: MediaOutputDialogManager,
) {
- fun onDeviceClick(expandable: Expandable) {
- activityStarter.startActivity(
- Intent(Settings.ACTION_BLUETOOTH_SETTINGS),
- true,
- expandable.activityTransitionController(),
- )
- }
-
fun onBarClick(session: MediaDeviceSession, expandable: Expandable) {
when (session) {
is MediaDeviceSession.Active -> {
- mediaOutputDialogFactory.createWithController(
+ mediaOutputDialogManager.createAndShowWithController(
session.packageName,
false,
expandable.dialogController()
)
}
is MediaDeviceSession.Inactive -> {
- mediaOutputDialogFactory.createDialogForSystemRouting(expandable.dialogController())
+ mediaOutputDialogManager.createAndShowForSystemRouting(
+ expandable.dialogController()
+ )
}
else -> {
/* do nothing */
@@ -68,7 +58,7 @@
cuj =
DialogCuj(
InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
- MediaOutputDialogFactory.INTERACTION_JANK_TAG
+ MediaOutputDialogManager.INTERACTION_JANK_TAG
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
index e518ed0..37bf661 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/DeviceIconViewModel.kt
@@ -26,13 +26,13 @@
val iconColor: Color
val backgroundColor: Color
- class IsPlaying(
+ data class IsPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
) : DeviceIconViewModel
- class IsNotPlaying(
+ data class IsNotPlaying(
override val icon: Icon,
override val iconColor: Color,
override val backgroundColor: Color,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 85d6c9e..37661b5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -113,11 +113,6 @@
private fun MediaDeviceSession.isPlaying(): Boolean =
this is MediaDeviceSession.Active && playbackState?.isActive == true
- fun onDeviceClick(expandable: Expandable) {
- actionsInteractor.onDeviceClick(expandable)
- volumePanelViewModel.dismissPanel()
- }
-
fun onBarClick(expandable: Expandable) {
actionsInteractor.onBarClick(mediaDeviceSession.value, expandable)
volumePanelViewModel.dismissPanel()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
index 9d801fc..9ef07fa 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/shared/model/VolumePanelComponents.kt
@@ -24,5 +24,6 @@
const val BOTTOM_BAR: VolumePanelComponentKey = "bottom_bar"
const val VOLUME_SLIDERS: VolumePanelComponentKey = "volume_sliders"
const val CAPTIONING: VolumePanelComponentKey = "captioning"
+ const val SPATIAL_AUDIO: VolumePanelComponentKey = "spatial_audio"
const val ANC: VolumePanelComponentKey = "anc"
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
index 4358611..6032bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt
@@ -71,10 +71,10 @@
combine(
currentAudioDeviceAttributes,
changes.onStart { emit(Unit) },
- spatializerInteractor.isHeadTrackingAvailable,
- ) { attributes, _, isHeadTrackingAvailable ->
+ ) { attributes, _,
+ ->
attributes ?: return@combine SpatialAudioAvailabilityModel.Unavailable
- if (isHeadTrackingAvailable) {
+ if (spatializerInteractor.isHeadTrackingAvailable(attributes)) {
return@combine SpatialAudioAvailabilityModel.HeadTracking
}
if (spatializerInteractor.isSpatialAudioAvailable(attributes)) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
index 4e65f60..9735e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/model/SpatialAudioEnabledModel.kt
@@ -19,6 +19,16 @@
/** Models spatial audio and head tracking enabled/disabled state. */
interface SpatialAudioEnabledModel {
+ companion object {
+ /** All possible SpatialAudioEnabledModel implementations. */
+ val values =
+ listOf(
+ Disabled,
+ SpatialAudioEnabled,
+ HeadTrackingEnabled,
+ )
+ }
+
/** Spatial audio is disabled. */
data object Disabled : SpatialAudioEnabledModel
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
new file mode 100644
index 0000000..9f9275b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioButtonViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+
+data class SpatialAudioButtonViewModel(
+ val model: SpatialAudioEnabledModel,
+ val button: ToggleButtonViewModel,
+ val iconColor: Color,
+ val labelColor: Color,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
new file mode 100644
index 0000000..30715d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.spatial.ui.viewmodel
+
+import android.content.Context
+import com.android.systemui.common.shared.model.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.res.R
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.ToggleButtonViewModel
+import com.android.systemui.volume.panel.component.button.ui.viewmodel.toButtonViewModel
+import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
+import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+@VolumePanelScope
+class SpatialAudioViewModel
+@Inject
+constructor(
+ @Application private val context: Context,
+ @VolumePanelScope private val scope: CoroutineScope,
+ availabilityCriteria: SpatialAudioAvailabilityCriteria,
+ private val interactor: SpatialAudioComponentInteractor,
+) {
+
+ val spatialAudioButton: StateFlow<ButtonViewModel?> =
+ interactor.isEnabled
+ .map { it.toViewModel(true).toButtonViewModel() }
+ .stateIn(scope, SharingStarted.Eagerly, null)
+
+ val isAvailable: StateFlow<Boolean> =
+ availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
+
+ val spatialAudioButtonByEnabled: StateFlow<List<SpatialAudioButtonViewModel>> =
+ combine(interactor.isEnabled, interactor.isAvailable) { currentIsEnabled, isAvailable ->
+ SpatialAudioEnabledModel.values
+ .filter {
+ if (it is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+ // Spatial audio control can be visible when there is spatial audio
+ // setting available but not the head tracking.
+ isAvailable is SpatialAudioAvailabilityModel.HeadTracking
+ } else {
+ true
+ }
+ }
+ .map { isEnabled ->
+ val isChecked = isEnabled == currentIsEnabled
+ val buttonViewModel: ToggleButtonViewModel =
+ isEnabled.toViewModel(isChecked)
+ SpatialAudioButtonViewModel(
+ button = buttonViewModel,
+ model = isEnabled,
+ iconColor =
+ Color.Attribute(
+ if (isChecked)
+ com.android.internal.R.attr.materialColorOnPrimaryContainer
+ else com.android.internal.R.attr.materialColorOnSurfaceVariant
+ ),
+ labelColor =
+ Color.Attribute(
+ if (isChecked)
+ com.android.internal.R.attr.materialColorOnSurface
+ else com.android.internal.R.attr.materialColorOutline
+ ),
+ )
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+ fun setEnabled(model: SpatialAudioEnabledModel) {
+ scope.launch { interactor.setEnabled(model) }
+ }
+
+ private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ToggleButtonViewModel {
+ if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_tracking)
+ )
+ }
+
+ if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_fixed)
+ )
+ }
+
+ if (this is SpatialAudioEnabledModel.Disabled) {
+ return ToggleButtonViewModel(
+ isChecked = isChecked,
+ icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+ label = context.getString(R.string.volume_panel_spatial_audio_off)
+ )
+ }
+
+ error("Unsupported model: $this")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
index f31ee86..d868c33 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/dagger/VolumePanelComponent.kt
@@ -20,6 +20,7 @@
import com.android.systemui.volume.panel.component.bottombar.BottomBarModule
import com.android.systemui.volume.panel.component.captioning.CaptioningModule
import com.android.systemui.volume.panel.component.mediaoutput.MediaOutputModule
+import com.android.systemui.volume.panel.component.spatialaudio.SpatialAudioModule
import com.android.systemui.volume.panel.component.volume.VolumeSlidersModule
import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
@@ -49,6 +50,7 @@
// Components modules
BottomBarModule::class,
AncModule::class,
+ SpatialAudioModule::class,
VolumeSlidersModule::class,
CaptioningModule::class,
MediaOutputModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
index 57ea997..999f4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/domain/DomainModule.kt
@@ -51,6 +51,7 @@
fun provideEnabledComponents(): Collection<VolumePanelComponentKey> {
return setOf(
VolumePanelComponents.ANC,
+ VolumePanelComponents.SPATIAL_AUDIO,
VolumePanelComponents.CAPTIONING,
VolumePanelComponents.VOLUME_SLIDERS,
VolumePanelComponents.MEDIA_OUTPUT,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
index ec4da06..8ba06e1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/ui/UiModule.kt
@@ -49,6 +49,7 @@
fun provideFooterComponents(): Collection<VolumePanelComponentKey> {
return listOf(
VolumePanelComponents.ANC,
+ VolumePanelComponents.SPATIAL_AUDIO,
VolumePanelComponents.CAPTIONING,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 24a5e80..2e04007 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -15,14 +15,11 @@
package com.android.systemui;
import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
import android.os.Looper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.statusbar.policy.FlashlightController;
-
import org.junit.Assert;
import org.junit.Test;
@@ -33,9 +30,9 @@
@Test
public void testClassDependency() {
- FlashlightController f = mock(FlashlightController.class);
- mDependency.injectTestDependency(FlashlightController.class, f);
- Assert.assertEquals(f, Dependency.get(FlashlightController.class));
+ FakeClass f = new FakeClass();
+ mDependency.injectTestDependency(FakeClass.class, f);
+ Assert.assertEquals(f, Dependency.get(FakeClass.class));
}
@Test
@@ -53,4 +50,8 @@
Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
}
+
+ private static class FakeClass {
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 3da7261..095c945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -22,10 +22,9 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
import android.content.Context;
import android.content.ContextWrapper;
import android.hardware.display.DisplayManager;
@@ -75,13 +74,13 @@
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
+ @Mock
private AccessibilityButtonTargetsObserver mTargetsObserver;
+ @Mock
private AccessibilityButtonModeObserver mModeObserver;
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
private KeyguardUpdateMonitorCallback mKeyguardCallback;
- private int mLastButtonMode;
- private String mLastButtonTargets;
@Mock
private SecureSettings mSecureSettings;
@@ -97,10 +96,14 @@
mWindowManager = mContext.getSystemService(WindowManager.class);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
- mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT);
+
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT));
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT));
}
@After
@@ -109,13 +112,6 @@
mController.onAccessibilityButtonTargetsChanged("");
mController = null;
}
-
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets,
- UserHandle.USER_CURRENT);
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode,
- UserHandle.USER_CURRENT);
}
@Test
@@ -227,9 +223,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -239,8 +234,8 @@
@Test
public void onAccessibilityButtonModeChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
+
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
@@ -250,9 +245,8 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -262,8 +256,7 @@
@Test
public void onAccessibilityButtonModeChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets()).thenReturn("");
mController = setUpController();
mController.onAccessibilityButtonModeChanged(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
@@ -273,9 +266,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndHasButtonTargets_showWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -285,9 +277,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_floatingModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -297,9 +288,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndHasButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -309,9 +299,8 @@
@Test
public void onAccessibilityButtonTargetsChanged_navBarModeAndNoButtonTargets_destroyWidget() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE,
- ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR, ActivityManager.getCurrentUser());
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged("");
@@ -321,9 +310,8 @@
@Test
public void onTargetsChanged_isFloatingViewLayerControllerCreated() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- UserHandle.USER_CURRENT);
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
mController = setUpController();
mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS);
@@ -335,8 +323,6 @@
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
- mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class));
- mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class));
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
@@ -348,12 +334,11 @@
}
private void enableAccessibilityFloatingMenuConfig() {
- Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU,
- ActivityManager.getCurrentUser());
- Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, TEST_A11Y_BTN_TARGETS,
- ActivityManager.getCurrentUser());
+ when(mTargetsObserver.getCurrentAccessibilityButtonTargets())
+ .thenReturn(TEST_A11Y_BTN_TARGETS);
+
+ when(mModeObserver.getCurrentAccessibilityButtonMode())
+ .thenReturn(ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU);
}
private void captureKeyguardUpdateMonitorCallback() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
index f07932c..e3be3822 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
@@ -19,6 +19,7 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.core.animation.doOnEnd
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.doOnEnd
@@ -30,6 +31,7 @@
@RunWith(AndroidTestingRunner::class)
@SmallTest
@RunWithLooper
+@FlakyTest(bugId = 302149604)
class AnimatorTestRuleOrderTest : SysuiTestCase() {
@get:Rule val animatorTestRule = AnimatorTestRule(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index 043dcaa..3c073d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -193,6 +193,34 @@
}
@Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modeEstimate_batteryPercentView_isNotNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNotNull()
+ // Make sure that it was added to the view hierarchy
+ assertThat(mBatteryMeterView.batteryPercentView.parent).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_NEW_STATUS_BAR_ICONS)
+ fun modePercent_batteryPercentView_isNull_flagOn() {
+ mBatteryMeterView.onBatteryLevelChanged(15, false)
+ mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ON)
+ mBatteryMeterView.setBatteryEstimateFetcher(Fetcher())
+
+ mBatteryMeterView.updatePercentText()
+
+ // New battery icon only uses the percent view for the estimate text
+ assertThat(mBatteryMeterView.batteryPercentView).isNull()
+ }
+
+ @Test
fun contentDescription_manyUpdates_alwaysUpdated() {
// BatteryDefender
mBatteryMeterView.onBatteryLevelChanged(90, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 7a18477..a569cee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -42,7 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -77,7 +77,8 @@
@Mock SysUiState mSysUiState;
@Mock
DialogTransitionAnimator mDialogTransitionAnimator;
- @Mock MediaOutputDialogFactory mMediaOutputDialogFactory;
+ @Mock
+ MediaOutputDialogManager mMediaOutputDialogManager;
private SystemUIDialog mDialog;
private TextView mTitle;
private TextView mSubTitle;
@@ -96,7 +97,7 @@
mBroadcastDialogDelegate = new BroadcastDialogDelegate(
mContext,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mLocalBluetoothManager,
new UiEventLoggerFake(),
mFakeExecutor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
index b25fb6e..30519b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/ui/view/MirroringConfirmationDialogTest.kt
@@ -16,14 +16,17 @@
package com.android.systemui.display.ui.view
+import android.graphics.Insets
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
+import android.view.WindowInsets
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -41,6 +44,7 @@
private val onStartMirroringCallback = mock<View.OnClickListener>()
private val onCancelCallback = mock<View.OnClickListener>()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -96,10 +100,40 @@
verify(onStartMirroringCallback).onClick(any())
}
+ @Test
+ fun onInsetsChanged_navBarInsets_updatesBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.navigationBars(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.navigationBars(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ @Test
+ fun onInsetsChanged_otherType_doesNotUpdateBottomPadding() {
+ dialog.show()
+
+ val insets = buildInsets(WindowInsets.Type.ime(), TEST_BOTTOM_INSETS)
+ dialog.onInsetsChanged(WindowInsets.Type.ime(), insets)
+
+ assertThat(dialog.requireViewById<View>(R.id.cd_bottom_sheet).paddingBottom)
+ .isNotEqualTo(TEST_BOTTOM_INSETS)
+ }
+
+ private fun buildInsets(@WindowInsets.Type.InsetsType type: Int, bottom: Int): WindowInsets {
+ return WindowInsets.Builder().setInsets(type, Insets.of(0, 0, 0, bottom)).build()
+ }
+
@After
fun teardown() {
if (::dialog.isInitialized) {
dialog.dismiss()
}
}
+
+ private companion object {
+ const val TEST_BOTTOM_INSETS = 1000 // arbitrarily high number
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 915522d..1a6da76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -53,9 +53,11 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -101,6 +103,8 @@
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -185,6 +189,7 @@
},
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 489665c..51828c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -6,6 +6,7 @@
import android.graphics.Point
import android.graphics.Rect
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
@@ -15,6 +16,7 @@
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
@@ -130,6 +132,7 @@
* surface, or the user will see the wallpaper briefly as the app animates in.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun noSurfaceAnimation_ifWakeAndUnlocking() {
whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
@@ -320,6 +323,7 @@
* If we are not wake and unlocking, we expect the unlock animation to play normally.
*/
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceAnimation_multipleTargets() {
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
arrayOf(remoteTarget1, remoteTarget2),
@@ -358,6 +362,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaOverriddenTo0_ifNotInteractive() {
whenever(powerManager.isInteractive).thenReturn(false)
@@ -389,6 +394,7 @@
}
@Test
+ @DisableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun surfaceBehindAlphaNotOverriddenTo0_ifInteractive() {
whenever(powerManager.isInteractive).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0957748..0bd4cbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1263,7 +1263,8 @@
mSystemPropertiesHelper,
() -> mock(WindowManagerLockscreenVisibilityManager.class),
mSelectedUserInteractor,
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ mock(WindowManagerOcclusionManager.class));
mViewMediator.start();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
index d0b1dd5..0bd541c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorTest.kt
@@ -20,16 +20,19 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -43,41 +46,35 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class BurnInInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val configurationRepository = kosmos.fakeConfigurationRepository
+ val fakeKeyguardRepository = kosmos.fakeKeyguardRepository
+
private val burnInOffset = 7
private var burnInProgress = 0f
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
- private lateinit var configurationRepository: FakeConfigurationRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
- private lateinit var fakeKeyguardRepository: FakeKeyguardRepository
- private lateinit var testScope: TestScope
private lateinit var underTest: BurnInInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- configurationRepository = FakeConfigurationRepository()
context
.getOrCreateTestableResources()
.addOverride(R.dimen.burn_in_prevention_offset_y, burnInOffset)
-
- KeyguardInteractorFactory.create().let {
- keyguardInteractor = it.keyguardInteractor
- fakeKeyguardRepository = it.repository
- }
whenever(burnInHelperWrapper.burnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset)
setBurnInProgress(.65f)
- testScope = TestScope()
underTest =
BurnInInteractor(
context,
burnInHelperWrapper,
- testScope.backgroundScope,
- configurationRepository,
- keyguardInteractor,
+ kosmos.applicationCoroutineScope,
+ kosmos.configurationInteractor,
+ kosmos.keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
new file mode 100644
index 0000000..7bef01a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromAodTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromAodTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to AOD and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.AOD,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() =
+ testScope.runTest {
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking up from wake and unlock should not start any transitions, we'll wait for the
+ // dismiss call.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ underTest.dismissAod()
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
new file mode 100644
index 0000000..258dbf3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.os.PowerManager
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import junit.framework.Assert.assertEquals
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDozingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDozingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToLockscreen_onWakeup() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGlanceableHub_onWakeup_ifIdleOnCommunal_noOccludingActivity() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Under default conditions, we should transition to LOCKSCREEN when waking up.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop_evenIfIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // Waking with a SHOW_WHEN_LOCKED activity on top should transition to OCCLUDED.
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ testScope.runTest {
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetectedAfterFinishedInAod_fromGone() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're GONE.
+ assertEquals(KeyguardState.GONE, kosmos.keyguardTransitionInteractor.getFinishedState())
+
+ // Get all the way to AOD
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should go to OCCLUDED - we came from GONE, but we finished in AOD, so this is no
+ // longer an insecure camera launch and it would be bad if we unlocked now.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromLockscreen() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.DOZING,
+ to = KeyguardState.LOCKSCREEN,
+ testScope,
+ )
+ runCurrent()
+
+ // Make sure we're in LOCKSCREEN.
+ assertEquals(
+ KeyguardState.LOCKSCREEN,
+ kosmos.keyguardTransitionInteractor.getFinishedState()
+ )
+
+ // Get part way to AOD.
+ powerInteractor.onStartedGoingToSleep(PowerManager.GO_TO_SLEEP_REASON_MIN)
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DOZING,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING,
+ )
+
+ // Detect a power gesture and then wake up.
+ reset(transitionRepository)
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ // We should head back to GONE since we started there.
+ assertThat(transitionRepository)
+ .startedTransition(from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
new file mode 100644
index 0000000..f534ba5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromDreamingTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to DOZING and set the power interactor asleep.
+ powerInteractor.setAsleepForTest()
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope
+ )
+ kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE)
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_ifDreamEnds_occludingActivityOnTop() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ kosmos.fakeKeyguardRepository.setDreaming(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testDoesNotTransitionToOccluded_occludingActivityOnTop_whileStillDreaming() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ runCurrent()
+
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToLockscreen_whenOccludingActivityEnds() =
+ testScope.runTest {
+ kosmos.fakeKeyguardRepository.setDreaming(true)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ runCurrent()
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 6aebe36..c3e24d5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.ActivityManager
+import android.app.WindowConfiguration
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -42,6 +45,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
@@ -171,4 +175,49 @@
assertThatRepository(transitionRepository).noTransitionsStarted()
}
+
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToOccluded_whenShowWhenLockedActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_STANDARD
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionsToDream_whenDreamActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ reset(transitionRepository)
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(
+ true,
+ ActivityManager.RunningTaskInfo().apply {
+ topActivityType = WindowConfiguration.ACTIVITY_TYPE_DREAM
+ }
+ )
+ runCurrent()
+
+ assertThatRepository(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
new file mode 100644
index 0000000..d3c4848
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.testKosmos
+import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromOccludedTransitionInteractorTest : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
+
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.fromOccludedTransitionInteractor
+
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Before
+ fun setup() {
+ underTest.start()
+
+ // Transition to OCCLUDED and set up PowerInteractor and the occlusion repository.
+ powerInteractor.setAwakeForTest()
+ runBlocking {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.OCCLUDED,
+ testScope
+ )
+ reset(transitionRepository)
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToLockscreen() =
+ testScope.runTest {
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testShowWhenLockedActivity_noLongerOnTop_transitionsToGlanceableHub_ifIdleOnCommunal() =
+ testScope.runTest {
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(onTop = false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.OCCLUDED,
+ to = KeyguardState.GLANCEABLE_HUB,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
index f33a5c9..7ee8963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -16,14 +16,23 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.selectedUserInteractor
@@ -31,20 +40,27 @@
import junit.framework.Assert.assertTrue
import junit.framework.Assert.fail
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FromPrimaryBouncerTransitionInteractorTest : SysuiTestCase() {
- val kosmos = testKosmos()
+ val kosmos =
+ testKosmos().apply {
+ this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
+ }
val underTest = kosmos.fromPrimaryBouncerTransitionInteractor
val testScope = kosmos.testScope
val selectedUserInteractor = kosmos.selectedUserInteractor
val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+ val bouncerRepository = kosmos.fakeKeyguardBouncerRepository
@Test
fun testSurfaceBehindVisibility() =
@@ -193,4 +209,85 @@
fail("surfaceBehindModel was unexpectedly null.")
}
}
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToLockscreen_whenBouncerHides() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testReturnToGlanceableHub_whenBouncerHides_ifIdleOnCommunal() =
+ testScope.runTest {
+ underTest.start()
+ kosmos.fakeCommunalRepository.setTransitionState(
+ flowOf(ObservableTransitionState.Idle(CommunalScenes.Communal))
+ )
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GLANCEABLE_HUB
+ )
+ }
+
+ @Test
+ @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun testTransitionToOccluded_bouncerHide_occludingActivityOnTop() =
+ testScope.runTest {
+ underTest.start()
+ bouncerRepository.setPrimaryShow(true)
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.PRIMARY_BOUNCER,
+ testScope
+ )
+
+ reset(transitionRepository)
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ // Shouldn't transition to OCCLUDED until the bouncer hides.
+ assertThat(transitionRepository).noTransitionsStarted()
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.OCCLUDED
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
new file mode 100644
index 0000000..8a77ed2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import kotlin.test.Test
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardOcclusionInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.keyguardOcclusionInteractor
+ private val powerInteractor = kosmos.powerInteractor
+ private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
+
+ @Test
+ fun testTransitionFromPowerGesture_whileGoingToSleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAsleep_isTrue() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertTrue(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileWaking_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testTransitionFromPowerGesture_whileAwake_isFalse() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ runCurrent()
+
+ assertFalse(underTest.shouldTransitionFromPowerButtonGesture())
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_notTrueSecondTime() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ false,
+ )
+
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ runCurrent()
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ true,
+ // Power button gesture was not triggered a second time, so this should remain
+ // false.
+ false,
+ )
+ }
+
+ @Test
+ fun testShowWhenLockedActivityLaunchedFromPowerGesture_falseIfReturningToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.showWhenLockedActivityLaunchedFromPowerGesture)
+ powerInteractor.setAwakeForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ powerInteractor.setAsleepForTest()
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ testScope = testScope,
+ throughTransitionState = TransitionState.RUNNING
+ )
+
+ powerInteractor.onCameraLaunchGestureDetected()
+ kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
+ powerInteractor.setAwakeForTest()
+ runCurrent()
+
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.AOD,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ assertThat(values)
+ .containsExactly(
+ false,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index d2a8444..45b2a42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -46,7 +46,9 @@
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
@@ -242,6 +244,8 @@
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var userTracker: UserTracker
+ private val kosmos = testKosmos()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -311,6 +315,7 @@
featureFlags = featureFlags,
)
.keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index c65a9ef..95606ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -21,8 +21,8 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
+import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
@@ -40,7 +40,6 @@
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -48,7 +47,6 @@
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.statusbar.commandQueue
import com.android.systemui.testKosmos
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -92,30 +90,26 @@
private var commandQueue = kosmos.fakeCommandQueue
private val shadeRepository = kosmos.fakeShadeRepository
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val transitionInteractor = kosmos.keyguardTransitionInteractor
private lateinit var featureFlags: FakeFeatureFlags
// Used to verify transition requests for test output
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
- @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor
- private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
- private lateinit var fromDozingTransitionInteractor: FromDozingTransitionInteractor
- private lateinit var fromOccludedTransitionInteractor: FromOccludedTransitionInteractor
- private lateinit var fromGoneTransitionInteractor: FromGoneTransitionInteractor
- private lateinit var fromAodTransitionInteractor: FromAodTransitionInteractor
- private lateinit var fromAlternateBouncerTransitionInteractor:
- FromAlternateBouncerTransitionInteractor
+ private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor
+ private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor
+ private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor
+ private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor
+ private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor
+ private val fromAlternateBouncerTransitionInteractor =
+ kosmos.fromAlternateBouncerTransitionInteractor
private val fromPrimaryBouncerTransitionInteractor =
kosmos.fromPrimaryBouncerTransitionInteractor
- private lateinit var fromDreamingLockscreenHostedTransitionInteractor:
- FromDreamingLockscreenHostedTransitionInteractor
- private lateinit var fromGlanceableHubTransitionInteractor:
- FromGlanceableHubTransitionInteractor
+ private val fromDreamingLockscreenHostedTransitionInteractor =
+ kosmos.fromDreamingLockscreenHostedTransitionInteractor
+ private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor
private val powerInteractor = kosmos.powerInteractor
- private val keyguardInteractor = kosmos.keyguardInteractor
private val communalInteractor = kosmos.communalInteractor
@Before
@@ -125,122 +119,21 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+ mSetFlagsRule.disableFlags(
+ Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR,
+ )
featureFlags = FakeFeatureFlags()
- val glanceableHubTransitions =
- GlanceableHubTransitions(
- bgDispatcher = kosmos.testDispatcher,
- transitionInteractor = transitionInteractor,
- transitionRepository = transitionRepository,
- communalInteractor = communalInteractor
- )
-
fromLockscreenTransitionInteractor.start()
fromPrimaryBouncerTransitionInteractor.start()
-
- fromDreamingTransitionInteractor =
- FromDreamingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- glanceableHubTransitions = glanceableHubTransitions,
- )
- .apply { start() }
-
- fromDreamingLockscreenHostedTransitionInteractor =
- FromDreamingLockscreenHostedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- )
- .apply { start() }
-
- fromAodTransitionInteractor =
- FromAodTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGoneTransitionInteractor =
- FromGoneTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromDozingTransitionInteractor =
- FromDozingTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromOccludedTransitionInteractor =
- FromOccludedTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- communalInteractor = communalInteractor,
- )
- .apply { start() }
-
- fromAlternateBouncerTransitionInteractor =
- FromAlternateBouncerTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- communalInteractor = communalInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- fromGlanceableHubTransitionInteractor =
- FromGlanceableHubTransitionInteractor(
- scope = testScope,
- bgDispatcher = kosmos.testDispatcher,
- mainDispatcher = kosmos.testDispatcher,
- glanceableHubTransitions = glanceableHubTransitions,
- keyguardInteractor = keyguardInteractor,
- transitionRepository = transitionRepository,
- transitionInteractor = transitionInteractor,
- powerInteractor = powerInteractor,
- )
- .apply { start() }
-
- mSetFlagsRule.disableFlags(
- FLAG_KEYGUARD_WM_STATE_REFACTOR,
- )
+ fromDreamingTransitionInteractor.start()
+ fromDreamingLockscreenHostedTransitionInteractor.start()
+ fromAodTransitionInteractor.start()
+ fromGoneTransitionInteractor.start()
+ fromDozingTransitionInteractor.start()
+ fromOccludedTransitionInteractor.start()
+ fromAlternateBouncerTransitionInteractor.start()
+ fromGlanceableHubTransitionInteractor.start()
}
@Test
@@ -257,7 +150,9 @@
.startedTransition(
to = KeyguardState.PRIMARY_BOUNCER,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncer)",
animatorAssertion = { it.isNotNull() }
)
@@ -282,7 +177,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -307,7 +202,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.OCCLUDED,
- ownerName = "FromOccludedTransitionInteractor",
+ ownerName = "FromOccludedTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -389,7 +284,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -414,7 +309,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.LOCKSCREEN,
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName = "FromLockscreenTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -703,6 +598,32 @@
coroutineContext.cancelChildren()
}
+ /** This handles security method NONE and screen off with lock timeout */
+ @Test
+ fun dreamingToGoneWithKeyguardNotShowing() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to DREAMING
+ keyguardRepository.setDreamingWithOverlay(true)
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING)
+ runCurrent()
+
+ // WHEN the device wakes up without a keyguard
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardRepository.setKeyguardDismissible(true)
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ assertThat(transitionRepository)
+ .startedTransition(
+ to = KeyguardState.GONE,
+ from = KeyguardState.DREAMING,
+ ownerName = "FromDreamingTransitionInteractor",
+ animatorAssertion = { it.isNotNull() }
+ )
+
+ coroutineContext.cancelChildren()
+ }
+
@Test
fun dozingToGlanceableHub() =
testScope.runTest {
@@ -752,7 +673,7 @@
.startedTransition(
to = KeyguardState.DOZING,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -777,7 +698,7 @@
.startedTransition(
to = KeyguardState.AOD,
from = KeyguardState.GONE,
- ownerName = "FromGoneTransitionInteractor",
+ ownerName = "FromGoneTransitionInteractor(Sleep transition triggered)",
animatorAssertion = { it.isNotNull() }
)
@@ -1044,12 +965,14 @@
@Test
fun primaryBouncerToAod() =
testScope.runTest {
+ // GIVEN aod available
+ keyguardRepository.setAodAvailable(true)
+ runCurrent()
+
// GIVEN a prior transition has run to PRIMARY_BOUNCER
bouncerRepository.setPrimaryShow(true)
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER)
- // GIVEN aod available and starting to sleep
- keyguardRepository.setAodAvailable(true)
powerInteractor.setAsleepForTest()
// WHEN the primaryBouncer stops showing
@@ -1059,7 +982,8 @@
// THEN a transition to AOD should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.AOD,
animatorAssertion = { it.isNotNull() },
@@ -1086,7 +1010,8 @@
// THEN a transition to DOZING should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromPrimaryBouncerTransitionInteractor",
+ ownerName =
+ "FromPrimaryBouncerTransitionInteractor" + "(Sleep transition triggered)",
from = KeyguardState.PRIMARY_BOUNCER,
to = KeyguardState.DOZING,
animatorAssertion = { it.isNotNull() },
@@ -1616,7 +1541,9 @@
// THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur
assertThat(transitionRepository)
.startedTransition(
- ownerName = "FromLockscreenTransitionInteractor",
+ ownerName =
+ "FromLockscreenTransitionInteractor" +
+ "(#listenForLockscreenToPrimaryBouncerDragging)",
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.PRIMARY_BOUNCER,
animatorAssertion = { it.isNull() }, // dragging should be manually animated
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index 87eee1a..0a29821 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -22,23 +22,25 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -53,18 +55,19 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
class UdfpsKeyguardInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+ val configRepository = kosmos.fakeConfigurationRepository
+ val keyguardRepository = kosmos.fakeKeyguardRepository
+
private val burnInProgress = 1f
private val burnInYOffset = 20
private val burnInXOffset = 10
- private lateinit var testScope: TestScope
- private lateinit var configRepository: FakeConfigurationRepository
private lateinit var bouncerRepository: KeyguardBouncerRepository
- private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var fakeCommandQueue: FakeCommandQueue
private lateinit var burnInInteractor: BurnInInteractor
private lateinit var shadeRepository: FakeShadeRepository
- private lateinit var keyguardInteractor: KeyguardInteractor
private lateinit var powerInteractor: PowerInteractor
@Mock private lateinit var burnInHelper: BurnInHelperWrapper
@@ -75,12 +78,6 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- testScope = TestScope()
- configRepository = FakeConfigurationRepository()
- KeyguardInteractorFactory.create().let {
- keyguardInteractor = it.keyguardInteractor
- keyguardRepository = it.repository
- }
bouncerRepository = FakeKeyguardBouncerRepository()
shadeRepository = FakeShadeRepository()
fakeCommandQueue = FakeCommandQueue()
@@ -89,8 +86,8 @@
context,
burnInHelper,
testScope.backgroundScope,
- configRepository,
- keyguardInteractor
+ kosmos.configurationInteractor,
+ kosmos.keyguardInteractor
)
powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -98,7 +95,7 @@
UdfpsKeyguardInteractor(
configRepository,
burnInInteractor,
- keyguardInteractor,
+ kosmos.keyguardInteractor,
shadeRepository,
dialogManager,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
index 02bd810..4bb0d47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt
@@ -27,11 +27,14 @@
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
@@ -71,7 +74,10 @@
fun isLongPressEnabled_unlocked() =
testScope.runTest {
val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled)
+ fingerprintPropertyRepository.supportsUdfps()
keyguardRepository.setKeyguardDismissible(true)
+ advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay
+ runCurrent()
assertThat(isLongPressEnabled).isTrue()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 2ec2fe3..7290863 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -54,6 +54,7 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -218,6 +219,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 1f14afa..bcec6109 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -280,6 +280,7 @@
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
+ shadeInteractor = shadeInteractor,
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
@@ -643,7 +644,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
@@ -673,7 +674,7 @@
val testConfig =
TestConfig(
- isVisible = true,
+ isVisible = false,
isClickable = false,
icon = mock(),
canShowWhileLocked = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 2f92afa..83e4d31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -84,7 +84,7 @@
import com.android.systemui.media.controls.ui.viewmodel.SeekBarViewModel
import com.android.systemui.media.controls.util.MediaFlags
import com.android.systemui.media.controls.util.MediaUiEventLogger
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.monet.ColorScheme
import com.android.systemui.monet.Style
import com.android.systemui.plugins.ActivityStarter
@@ -160,7 +160,7 @@
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var expandedSet: ConstraintSet
@Mock private lateinit var collapsedSet: ConstraintSet
- @Mock private lateinit var mediaOutputDialogFactory: MediaOutputDialogFactory
+ @Mock private lateinit var mediaOutputDialogManager: MediaOutputDialogManager
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var transitionParent: ViewGroup
@@ -266,7 +266,7 @@
mediaViewController,
seekBarViewModel,
Lazy { mediaDataManager },
- mediaOutputDialogFactory,
+ mediaOutputDialogManager,
mediaCarouselController,
falsingManager,
clock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ca403e0..695d3b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -19,7 +19,6 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -48,7 +47,6 @@
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
-import com.android.settingslib.media.LocalMediaManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -129,12 +127,6 @@
mNearbyMediaDevicesManager, mAudioManager, mPowerExemptionManager,
mKeyguardManager, mFlags, mUserTracker);
- // Using a fake package will cause routing operations to fail, so we intercept
- // scanning-related operations.
- mMediaOutputController.mLocalMediaManager = mock(LocalMediaManager.class);
- doNothing().when(mMediaOutputController.mLocalMediaManager).startScan();
- doNothing().when(mMediaOutputController.mLocalMediaManager).stopScan();
-
mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
mMediaOutputController);
mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 0879884..83def8e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -42,16 +42,16 @@
private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
- private final MediaOutputDialogFactory mMockMediaOutputDialogFactory =
- mock(MediaOutputDialogFactory.class);
+ private final MediaOutputDialogManager mMockMediaOutputDialogManager =
+ mock(MediaOutputDialogManager.class);
- private final MediaOutputBroadcastDialogFactory mMockMediaOutputBroadcastDialogFactory =
- mock(MediaOutputBroadcastDialogFactory.class);
+ private final MediaOutputBroadcastDialogManager mMockMediaOutputBroadcastDialogManager =
+ mock(MediaOutputBroadcastDialogManager.class);
@Before
public void setup() {
- mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogFactory,
- mMockMediaOutputBroadcastDialogFactory);
+ mMediaOutputDialogReceiver = new MediaOutputDialogReceiver(mMockMediaOutputDialogManager,
+ mMockMediaOutputBroadcastDialogManager);
}
@Test
@@ -60,9 +60,10 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, times(1))
- .create(getContext().getPackageName(), false, null);
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), false, null);
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -71,8 +72,9 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -80,8 +82,9 @@
Intent intent = new Intent(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -92,8 +95,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -104,9 +108,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, times(1))
- .create(getContext().getPackageName(), true, null);
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, times(1))
+ .createAndShow(getContext().getPackageName(), true, null);
}
@Test
@@ -117,8 +121,9 @@
intent.putExtra("Wrong Package Name Key", getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -128,8 +133,9 @@
MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG);
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -139,8 +145,9 @@
intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName());
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
@Test
@@ -148,7 +155,8 @@
Intent intent = new Intent("UnKnown Action");
mMediaOutputDialogReceiver.onReceive(getContext(), intent);
- verify(mMockMediaOutputDialogFactory, never()).create(any(), anyBoolean(), any());
- verify(mMockMediaOutputBroadcastDialogFactory, never()).create(any(), anyBoolean(), any());
+ verify(mMockMediaOutputDialogManager, never()).createAndShow(any(), anyBoolean(), any());
+ verify(mMockMediaOutputBroadcastDialogManager, never())
+ .createAndShow(any(), anyBoolean(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index dbfab64..bda0e1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -21,32 +21,25 @@
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.activityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
- private val fakeActivityTaskManager = FakeActivityTaskManager()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val repo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
+ private val kosmos = taskSwitcherKosmos()
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val testScope = kosmos.testScope
+ private val repo = kosmos.activityTaskManagerTasksRepository
@Test
fun launchRecentTask_taskIsMovedToForeground() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index fdd434a..6043ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,50 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
import android.os.Binder
-import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createToken
import com.android.systemui.mediaprojection.taskswitcher.data.model.MediaProjectionState
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createToken
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.mediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
- private val fakeActivityTaskManager = FakeActivityTaskManager()
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val repo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper
- )
+ private val repo = kosmos.mediaProjectionManagerRepository
@Test
fun switchProjectedTask_stateIsUpdatedWithNewTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index dfb688b..33e65f26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,55 +17,33 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
import com.android.systemui.mediaprojection.taskswitcher.domain.model.TaskSwitchState
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherInteractor
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitchInteractorTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val interactor = kosmos.taskSwitcherInteractor
@Test
fun taskSwitchChanges_notProjecting_foregroundTaskChange_emitsNotProjectingTask() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index c4e9393..9382c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,26 +18,22 @@
import android.app.Notification
import android.app.NotificationManager
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
-import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -46,39 +42,16 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
private val notificationManager = mock<NotificationManager>()
-
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
private lateinit var coordinator: TaskSwitcherNotificationCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index 5dadf21..a468953 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,60 +17,35 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
-import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeActivityTaskManager.Companion.createTask
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createDisplaySession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.FakeMediaProjectionManager.Companion.createSingleTaskSession
-import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
-import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createDisplaySession
+import com.android.systemui.mediaprojection.taskswitcher.FakeMediaProjectionManager.Companion.createSingleTaskSession
+import com.android.systemui.mediaprojection.taskswitcher.fakeActivityTaskManager
+import com.android.systemui.mediaprojection.taskswitcher.fakeMediaProjectionManager
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherKosmos
+import com.android.systemui.mediaprojection.taskswitcher.taskSwitcherViewModel
import com.android.systemui.mediaprojection.taskswitcher.ui.model.TaskSwitcherNotificationUiState
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel.Companion.NOTIFICATION_MAX_SHOW_DURATION
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidTestingRunner::class)
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
- private val dispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(dispatcher)
-
- private val fakeActivityTaskManager = FakeActivityTaskManager()
- private val fakeMediaProjectionManager = FakeMediaProjectionManager()
-
- private val tasksRepo =
- ActivityTaskManagerTasksRepository(
- activityTaskManager = fakeActivityTaskManager.activityTaskManager,
- applicationScope = testScope.backgroundScope,
- backgroundDispatcher = dispatcher
- )
-
- private val mediaRepo =
- MediaProjectionManagerRepository(
- mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
- handler = Handler.getMain(),
- applicationScope = testScope.backgroundScope,
- tasksRepository = tasksRepo,
- backgroundDispatcher = dispatcher,
- mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
- )
-
- private val interactor = TaskSwitchInteractor(mediaRepo, tasksRepo)
-
- private val viewModel =
- TaskSwitcherNotificationViewModel(interactor, backgroundDispatcher = dispatcher)
+ private val kosmos = taskSwitcherKosmos()
+ private val testScope = kosmos.testScope
+ private val fakeActivityTaskManager = kosmos.fakeActivityTaskManager
+ private val fakeMediaProjectionManager = kosmos.fakeMediaProjectionManager
+ private val viewModel = kosmos.taskSwitcherViewModel
@Test
fun uiState_notProjecting_emitsNotShowing() =
@@ -138,6 +113,41 @@
}
@Test
+ fun uiState_taskChanged_beforeDelayLimit_stillEmitsShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION - 1.milliseconds)
+ assertThat(uiState)
+ .isEqualTo(TaskSwitcherNotificationUiState.Showing(projectedTask, foregroundTask))
+ }
+
+ @Test
+ fun uiState_taskChanged_afterDelayLimit_emitsNotShowing() =
+ testScope.runTest {
+ val projectedTask = createTask(taskId = 1)
+ val foregroundTask = createTask(taskId = 2)
+ val uiState by collectLastValue(viewModel.uiState)
+
+ fakeActivityTaskManager.addRunningTasks(projectedTask, foregroundTask)
+ fakeMediaProjectionManager.dispatchOnSessionSet(
+ session = createSingleTaskSession(projectedTask.token.asBinder())
+ )
+ fakeActivityTaskManager.moveTaskToForeground(foregroundTask)
+
+ testScheduler.advanceTimeBy(NOTIFICATION_MAX_SHOW_DURATION)
+ assertThat(uiState).isEqualTo(TaskSwitcherNotificationUiState.NotShowing)
+ }
+
+ @Test
fun uiState_projectingTask_foregroundTaskChanged_thenTaskSwitched_emitsNotShowing() =
testScope.runTest {
val projectedTask = createTask(taskId = 1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
index 52859cd..d405df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java
@@ -43,13 +43,11 @@
import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
import androidx.test.filters.SmallTest;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
-import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
@@ -61,7 +59,9 @@
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -76,7 +76,6 @@
/** atest NavigationBarControllerTest */
@RunWith(AndroidTestingRunner.class)
-@RunWithLooper
@SmallTest
public class NavigationBarControllerImplTest extends SysuiTestCase {
@@ -88,6 +87,8 @@
private StaticMockitoSession mMockitoSession;
private FakeDisplayTracker mDisplayTracker = new FakeDisplayTracker(mContext);
+ private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+
@Mock
private CommandQueue mCommandQueue;
@Mock
@@ -104,7 +105,7 @@
mock(NavigationModeController.class),
mock(SysUiState.class),
mCommandQueue,
- Dependency.get(Dependency.MAIN_HANDLER),
+ mExecutor,
mock(ConfigurationController.class),
mock(NavBarHelper.class),
mTaskbarDelegate,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index 7b285ab..2e8160b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -33,26 +33,26 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.mediaprojection.SessionCreationSource
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate
import com.android.systemui.model.SysUiState
import com.android.systemui.qs.tiles.RecordIssueTile
import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
@@ -70,16 +70,19 @@
@Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
@Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
@Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
- @Mock private lateinit var userContextProvider: UserContextProvider
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var userFileManager: UserFileManager
@Mock private lateinit var sharedPreferences: SharedPreferences
+ @Mock
+ private lateinit var screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate
+ @Mock private lateinit var screenCaptureDisabledDialog: SystemUIDialog
@Mock private lateinit var sysuiState: SysUiState
@Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
- @Mock private lateinit var bgExecutor: Executor
- @Mock private lateinit var mainExecutor: Executor
+ private val systemClock = FakeSystemClock()
+ private val bgExecutor = FakeExecutor(systemClock)
+ private val mainExecutor = FakeExecutor(systemClock)
@Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
private lateinit var dialog: SystemUIDialog
@@ -91,7 +94,8 @@
MockitoAnnotations.initMocks(this)
whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
- whenever(userContextProvider.userContext).thenReturn(mContext)
+ whenever(screenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(screenCaptureDisabledDialog)
whenever(
userFileManager.getSharedPreferences(
eq(RecordIssueTile.TILE_SPEC),
@@ -116,7 +120,6 @@
dialog =
RecordIssueDialogDelegate(
factory,
- userContextProvider,
userTracker,
flags,
bgExecutor,
@@ -124,6 +127,7 @@
dprLazy,
mediaProjectionMetricsLogger,
userFileManager,
+ screenCaptureDisabledDialogDelegate,
) {
latch.countDown()
}
@@ -163,13 +167,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger, never())
.notifyProjectionInitiated(
@@ -192,13 +191,8 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
-
- val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(mainExecutor).execute(mainCaptor.capture())
- mainCaptor.value.run()
+ bgExecutor.runAllReady()
+ mainExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
@@ -219,9 +213,7 @@
val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
screenRecordSwitch.isChecked = true
- val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
- verify(bgExecutor).execute(bgCaptor.capture())
- bgCaptor.value.run()
+ bgExecutor.runAllReady()
verify(mediaProjectionMetricsLogger)
.notifyProjectionInitiated(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6cbe8c9..b3df12ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,13 +17,11 @@
package com.android.systemui.screenrecord;
import static android.os.Process.myUid;
-
import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -48,10 +46,9 @@
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
import com.android.systemui.mediaprojection.SessionCreationSource;
import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
-import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
+import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialogDelegate;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.DialogDelegate;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -84,8 +81,6 @@
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private UserContextProvider mUserContextProvider;
- @Mock
private ScreenCaptureDevicePolicyResolver mDevicePolicyResolver;
@Mock
private DialogTransitionAnimator mDialogTransitionAnimator;
@@ -96,6 +91,22 @@
@Mock
private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
+ @Mock
+ private ScreenCaptureDisabledDialogDelegate mScreenCaptureDisabledDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenCaptureDisabledDialog;
+ @Mock
+ private ScreenRecordDialogDelegate.Factory mScreenRecordDialogFactory;
+ @Mock
+ private ScreenRecordDialogDelegate mScreenRecordDialogDelegate;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate.Factory
+ mScreenRecordPermissionDialogDelegateFactory;
+ @Mock
+ private ScreenRecordPermissionDialogDelegate mScreenRecordPermissionDialogDelegate;
+ @Mock
+ private SystemUIDialog mScreenRecordSystemUIDialog;
+
private FakeFeatureFlags mFeatureFlags;
private RecordingController mController;
private TestSystemUIDialogFactory mDialogFactory;
@@ -108,8 +119,6 @@
Context spiedContext = spy(mContext);
when(spiedContext.getUserId()).thenReturn(TEST_USER_ID);
- when(mUserContextProvider.getUserContext()).thenReturn(spiedContext);
-
mDialogFactory = new TestSystemUIDialogFactory(
mContext,
Dependency.get(SystemUIDialogManager.class),
@@ -119,16 +128,26 @@
);
mFeatureFlags = new FakeFeatureFlags();
+ when(mScreenCaptureDisabledDialogDelegate.createDialog())
+ .thenReturn(mScreenCaptureDisabledDialog);
+ when(mScreenRecordDialogFactory.create(any(), any()))
+ .thenReturn(mScreenRecordDialogDelegate);
+ when(mScreenRecordDialogDelegate.createDialog()).thenReturn(mScreenRecordSystemUIDialog);
+ when(mScreenRecordPermissionDialogDelegateFactory.create(any(), any(), anyInt(), any()))
+ .thenReturn(mScreenRecordPermissionDialogDelegate);
+ when(mScreenRecordPermissionDialogDelegate.createDialog())
+ .thenReturn(mScreenRecordSystemUIDialog);
mController = new RecordingController(
mMainExecutor,
mBroadcastDispatcher,
- mContext,
mFeatureFlags,
- mUserContextProvider,
() -> mDevicePolicyResolver,
mUserTracker,
mMediaProjectionMetricsLogger,
- mDialogFactory);
+ mScreenCaptureDisabledDialogDelegate,
+ mScreenRecordDialogFactory,
+ mScreenRecordPermissionDialogDelegateFactory
+ );
mController.addCallback(mCallback);
}
@@ -242,8 +261,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
@@ -255,7 +274,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenRecordDialog.class);
+ assertThat(dialog).isEqualTo(mScreenRecordSystemUIDialog);
}
@Test
@@ -267,7 +286,7 @@
Dialog dialog = mController.createScreenRecordDialog(mContext, mFeatureFlags,
mDialogTransitionAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
- assertThat(dialog).isInstanceOf(ScreenCaptureDisabledDialog.class);
+ assertThat(dialog).isEqualTo(mScreenCaptureDisabledDialog);
}
@Test
@@ -284,8 +303,8 @@
mActivityStarter,
/* onStartRecordingClicked= */ null);
- assertThat(dialog).isSameInstanceAs(mDialogFactory.mLastCreatedDialog);
- assertThat(mDialogFactory.mLastDelegate)
+ assertThat(dialog).isSameInstanceAs(mScreenRecordSystemUIDialog);
+ assertThat(mScreenRecordPermissionDialogDelegate)
.isInstanceOf(ScreenRecordPermissionDialogDelegate.class);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
index 90ced92..6e48074 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegateTest.kt
@@ -39,6 +39,7 @@
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
@@ -58,6 +59,7 @@
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ScreenRecordPermissionDialogDelegateTest : SysuiTestCase() {
+ //@Mock private lateinit var dialogFactory: SystemUIDialog.Factory
@Mock private lateinit var starter: ActivityStarter
@Mock private lateinit var controller: RecordingController
@Mock private lateinit var userContextProvider: UserContextProvider
@@ -71,14 +73,17 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+
val systemUIDialogFactory =
- SystemUIDialog.Factory(
- context,
- Dependency.get(SystemUIDialogManager::class.java),
- Dependency.get(SysUiState::class.java),
- Dependency.get(BroadcastDispatcher::class.java),
- Dependency.get(DialogTransitionAnimator::class.java),
- )
+ SystemUIDialog.Factory(
+ context,
+ Dependency.get(SystemUIDialogManager::class.java),
+ Dependency.get(SysUiState::class.java),
+ Dependency.get(BroadcastDispatcher::class.java),
+ Dependency.get(DialogTransitionAnimator::class.java),
+ )
+
val delegate =
ScreenRecordPermissionDialogDelegate(
UserHandle.of(0),
@@ -88,11 +93,9 @@
userContextProvider,
onStartRecordingClicked,
mediaProjectionMetricsLogger,
- systemUIDialogFactory
+ systemUIDialogFactory,
)
dialog = delegate.createDialog()
- delegate.onCreate(dialog, savedInstanceState = null)
- whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
index 2f911fff..92c2404 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotSoundControllerTest.kt
@@ -22,8 +22,10 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import java.lang.IllegalStateException
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -31,12 +33,14 @@
import org.mockito.Mockito.verify
@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
class ScreenshotSoundControllerTest : SysuiTestCase() {
private val soundProvider = mock<ScreenshotSoundProvider>()
private val mediaPlayer = mock<MediaPlayer>()
private val bgDispatcher = UnconfinedTestDispatcher()
private val scope = TestScope(bgDispatcher)
+
@Before
fun setup() {
whenever(soundProvider.getScreenshotSound()).thenReturn(mediaPlayer)
@@ -45,52 +49,59 @@
@Test
fun init_soundLoading() {
createController()
- bgDispatcher.scheduler.runCurrent()
+ scope.advanceUntilIdle()
verify(soundProvider).getScreenshotSound()
}
@Test
- fun init_soundLoadingException_playAndReleaseDoNotThrow() = runTest {
- whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
+ fun init_soundLoadingException_playAndReleaseDoNotThrow() =
+ scope.runTest {
+ whenever(soundProvider.getScreenshotSound()).thenThrow(IllegalStateException())
- val controller = createController()
+ val controller = createController()
- controller.playCameraSound().await()
- controller.releaseScreenshotSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer, never()).start()
- verify(mediaPlayer, never()).release()
- }
+ verify(mediaPlayer, never()).start()
+ verify(mediaPlayer, never()).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerPlays() =
+ scope.runTest {
+ val controller = createController()
- controller.playCameraSound().await()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- }
+ verify(mediaPlayer).start()
+ }
@Test
- fun playCameraSound_illegalStateException_doesNotThrow() = runTest {
- whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
+ fun playCameraSound_illegalStateException_doesNotThrow() =
+ scope.runTest {
+ whenever(mediaPlayer.start()).thenThrow(IllegalStateException())
- val controller = createController()
- controller.playCameraSound().await()
+ val controller = createController()
+ controller.playScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).start()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).start()
+ verify(mediaPlayer).release()
+ }
@Test
- fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() = runTest {
- val controller = createController()
+ fun playCameraSound_soundLoadingSuccessful_mediaPlayerReleases() =
+ scope.runTest {
+ val controller = createController()
- controller.releaseScreenshotSound().await()
+ controller.releaseScreenshotSound()
+ advanceUntilIdle()
- verify(mediaPlayer).release()
- }
+ verify(mediaPlayer).release()
+ }
private fun createController() =
ScreenshotSoundControllerImpl(soundProvider, scope, bgDispatcher)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index f8771b2..cdff4d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -287,7 +287,7 @@
@Mock protected KeyguardMediaController mKeyguardMediaController;
@Mock protected NavigationModeController mNavigationModeController;
@Mock protected NavigationBarController mNavigationBarController;
- @Mock protected QuickSettingsController mQsController;
+ @Mock protected QuickSettingsControllerImpl mQsController;
@Mock protected ShadeHeaderController mShadeHeaderController;
@Mock protected ContentResolver mContentResolver;
@Mock protected TapAgainViewController mTapAgainViewController;
@@ -380,7 +380,7 @@
protected final ShadeExpansionStateManager mShadeExpansionStateManager =
new ShadeExpansionStateManager();
- protected QuickSettingsController mQuickSettingsController;
+ protected QuickSettingsControllerImpl mQuickSettingsController;
@Mock protected Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
protected FragmentHostManager.FragmentListener mFragmentListener;
@@ -793,7 +793,7 @@
when(mNotificationPanelViewControllerLazy.get())
.thenReturn(mNotificationPanelViewController);
- mQuickSettingsController = new QuickSettingsController(
+ mQuickSettingsController = new QuickSettingsControllerImpl(
mNotificationPanelViewControllerLazy,
mView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 960fd59..617b25d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -112,7 +112,7 @@
@Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
- @Mock private lateinit var quickSettingsController: QuickSettingsController
+ @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var lockIconViewController: LockIconViewController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 0b49a95..4809a47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -109,7 +109,7 @@
import kotlinx.coroutines.test.TestScope;
-public class QuickSettingsControllerBaseTest extends SysuiTestCase {
+public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
protected static final float QS_FRAME_START_X = 0f;
protected static final int QS_FRAME_WIDTH = 1000;
protected static final int QS_FRAME_TOP = 0;
@@ -119,7 +119,7 @@
protected static final int DEFAULT_MIN_HEIGHT_SPLIT_SHADE = DEFAULT_HEIGHT;
protected static final int DEFAULT_MIN_HEIGHT = 300;
- protected QuickSettingsController mQsController;
+ protected QuickSettingsControllerImpl mQsController;
protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected TestScope mTestScope = mKosmos.getTestScope();
@@ -304,7 +304,7 @@
mMainHandler = new Handler(Looper.getMainLooper());
- mQsController = new QuickSettingsController(
+ mQsController = new QuickSettingsControllerImpl(
mPanelViewControllerLazy,
mPanelView,
mQsFrameTranslateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 997e0e2..b16f412 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -41,8 +41,8 @@
import androidx.test.filters.SmallTest;
-import com.android.systemui.res.R;
import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.res.R;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,7 +53,7 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class QuickSettingsControllerTest extends QuickSettingsControllerBaseTest {
+public class QuickSettingsControllerImplTest extends QuickSettingsControllerImplBaseTest {
@Test
public void testCloseQsSideEffects() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
index cc4a063..2c453a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt
@@ -27,7 +27,7 @@
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
-class QuickSettingsControllerWithCoroutinesTest : QuickSettingsControllerBaseTest() {
+class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImplBaseTest() {
@Test
fun isExpansionEnabled_dozing_false() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
index a59ba07..eb692eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java
@@ -32,9 +32,9 @@
import android.view.LayoutInflater;
import android.view.View;
-import com.android.systemui.res.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
+import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +51,7 @@
import java.util.List;
import java.util.Map;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -65,7 +66,7 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(MediaOutputDialogFactory.class);
+ mDependency.injectMockDependency(MediaOutputDialogManager.class);
allowTestableLooperAsMainThread();
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mDynamicChildBindController =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index b3fc25c..24195fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -16,8 +16,10 @@
package com.android.systemui.statusbar.notification.icon.ui.viewmodel
+import android.platform.test.annotations.DisableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
import com.android.systemui.SysUITestComponent
import com.android.systemui.SysUITestModule
@@ -238,6 +240,7 @@
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun animationsEnabled_isTrue_whenKeyguardIsShowing() =
testComponent.runTest {
keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e78081f..718f998 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -58,7 +58,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.media.controls.util.MediaFeatureFlag;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -89,6 +89,8 @@
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
+import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
@@ -136,6 +138,8 @@
public final Runnable mFutureDismissalRunnable;
private @InflationFlag int mDefaultInflationFlags;
private final FakeFeatureFlags mFeatureFlags;
+ private final SystemClock mSystemClock;
+ private final RowInflaterTaskLogger mRowInflaterTaskLogger;
public NotificationTestHelper(
Context context,
@@ -155,7 +159,7 @@
dependency.injectTestDependency(FeatureFlags.class, mFeatureFlags);
dependency.injectMockDependency(NotificationMediaManager.class);
dependency.injectMockDependency(NotificationShadeWindowController.class);
- dependency.injectMockDependency(MediaOutputDialogFactory.class);
+ dependency.injectMockDependency(MediaOutputDialogManager.class);
mMockLogger = mock(ExpandableNotificationRowLogger.class);
mStatusBarStateController = mock(StatusBarStateController.class);
mKeyguardBypassController = mock(KeyguardBypassController.class);
@@ -199,6 +203,9 @@
mFutureDismissalRunnable = mock(Runnable.class);
when(mOnUserInteractionCallback.registerFutureDismissal(any(), anyInt()))
.thenReturn(mFutureDismissalRunnable);
+
+ mSystemClock = new SystemClockImpl();
+ mRowInflaterTaskLogger = mock(RowInflaterTaskLogger.class);
}
public void setDefaultInflationFlags(@InflationFlag int defaultInflationFlags) {
@@ -572,7 +579,8 @@
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
if (com.android.systemui.Flags.notificationRowUserContext()) {
- inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+ inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry, mSystemClock,
+ mRowInflaterTaskLogger));
}
mRow = (ExpandableNotificationRow) inflater.inflate(
R.layout.status_bar_notification_row,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
new file mode 100644
index 0000000..f88bd7e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(NotificationViewFlipperPausing.FLAG_NAME)
+class NotificationViewFlipperViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ val underTest
+ get() = kosmos.notificationViewFlipperViewModel
+
+ @Test
+ fun testIsPaused_falseWhenViewingShade() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ runCurrent()
+
+ // THEN view flippers should NOT be paused
+ assertThat(isPaused).isFalse()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenViewingKeyguard() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN on keyguard
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+
+ @Test
+ fun testIsPaused_trueWhenStartingToSleep() =
+ kosmos.testScope.runTest {
+ val isPaused by collectLastValue(underTest.isPaused)
+
+ // WHEN shade is open
+ kosmos.fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ // AND device is starting to go to sleep
+ kosmos.fakePowerRepository.updateWakefulness(WakefulnessState.STARTING_TO_SLEEP)
+ runCurrent()
+
+ // THEN view flippers should be paused
+ assertThat(isPaused).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 3d75288..4715b33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -57,39 +57,6 @@
)
}
- // region isDimmed
- @Test
- fun isDimmed_whenTrue_shouldReturnTrue() {
- sut.arrangeDimmed(true)
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenFalse_shouldReturnFalse() {
- sut.arrangeDimmed(false)
-
- assertThat(sut.isDimmed).isFalse()
- }
-
- @Test
- fun isDimmed_whenDozeAmountIsEmpty_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.dozeAmount = 0f
-
- assertThat(sut.isDimmed).isTrue()
- }
-
- @Test
- fun isDimmed_whenPulseExpandingIsFalse_shouldReturnTrue() {
- sut.arrangeDimmed(true)
- sut.arrangePulseExpanding(false)
- sut.dozeAmount = 1f // arrangePulseExpanding changes dozeAmount
-
- assertThat(sut.isDimmed).isTrue()
- }
- // endregion
-
// region pulseHeight
@Test
fun pulseHeight_whenValueChanged_shouldCallListener() {
@@ -383,12 +350,6 @@
}
// region Arrange helper methods.
-private fun AmbientState.arrangeDimmed(value: Boolean) {
- isDimmed = value
- dozeAmount = if (value) 0f else 1f
- arrangePulseExpanding(!value)
-}
-
private fun AmbientState.arrangePulseExpanding(value: Boolean) {
if (value) {
dozeAmount = 1f
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index f326cea..220305c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -38,7 +38,6 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -73,11 +72,11 @@
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
-import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -97,13 +96,11 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -232,6 +229,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testUpdateStackHeight_qsExpansionZero() {
final float expansionFraction = 0.2f;
final float overExpansion = 50f;
@@ -307,14 +305,6 @@
}
@Test
- public void testNotDimmedOnKeyguard() {
- when(mBarState.getState()).thenReturn(StatusBarState.SHADE);
- mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
- mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
- assertFalse(mStackScroller.isDimmed());
- }
-
- @Test
public void updateEmptyView_dndSuppressing() {
when(mEmptyShadeView.willBeGone()).thenReturn(true);
@@ -738,6 +728,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_noOffset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(0, 0, 1000, 1000);
@@ -754,6 +745,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address lack of QS Header
public void testInsideQSHeader_Offset() {
ViewGroup qsHeader = mock(ViewGroup.class);
Rect boundsOnScreen = new Rect(100, 100, 1000, 1000);
@@ -773,12 +765,14 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void setFractionToShade_recomputesStackHeight() {
mStackScroller.setFractionToShade(1f);
verify(mNotificationStackSizeCalculator).computeHeight(any(), anyInt(), anyFloat());
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetOwnScrollY_shadeNotClosing_scrollYChanges() {
// Given: shade is not closing, scrollY is 0
mAmbientState.setScrollY(0);
@@ -877,6 +871,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSplitShade_hasTopOverscroll() {
mTestableResources
.addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
@@ -949,6 +944,7 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER) // TODO(b/312473478): address disabled test
public void testSetMaxDisplayedNotifications_notifiesListeners() {
ExpandableView.OnHeightChangedListener listener =
mock(ExpandableView.OnHeightChangedListener.class);
@@ -963,9 +959,8 @@
}
@Test
+ @DisableFlags(FLAG_SCENE_CONTAINER)
public void testDispatchTouchEvent_sceneContainerDisabled() {
- Assume.assumeFalse(SceneContainerFlag.isEnabled());
-
MotionEvent event = MotionEvent.obtain(
SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(),
@@ -981,34 +976,60 @@
}
@Test
+ @EnableSceneContainer
public void testDispatchTouchEvent_sceneContainerEnabled() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
mStackScroller.setIsBeingDragged(true);
- MotionEvent moveEvent = MotionEvent.obtain(
- SystemClock.uptimeMillis(),
- SystemClock.uptimeMillis(),
+ long downTime = SystemClock.uptimeMillis() - 100;
+ MotionEvent moveEvent1 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
MotionEvent.ACTION_MOVE,
- 0,
- 0,
+ 101,
+ 201,
0
);
- MotionEvent syntheticDownEvent = moveEvent.copy();
+ MotionEvent syntheticDownEvent = moveEvent1.copy();
syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ mStackScroller.dispatchTouchEvent(moveEvent1);
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(syntheticDownEvent);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
- mStackScroller.dispatchTouchEvent(moveEvent);
+ MotionEvent moveEvent2 = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 102,
+ 202,
+ 0
+ );
- verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ mStackScroller.dispatchTouchEvent(moveEvent2);
+
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(moveEvent2);
+ assertTrue(mStackScroller.getIsBeingDragged());
+ clearInvocations(mStackScrollLayoutController);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ /* downTime= */ downTime,
+ /* eventTime= */ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 103,
+ 203,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ assertThatMotionEvent(captureTouchSentToSceneFramework()).matches(upEvent);
+ assertFalse(mStackScroller.getIsBeingDragged());
}
@Test
- @EnableFlags(FLAG_SCENE_CONTAINER)
- public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
- Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ @EnableSceneContainer
+ public void testDispatchTouchEvent_sceneContainerEnabled_ignoresInitialActionUp() {
mStackScroller.setIsBeingDragged(true);
MotionEvent upEvent = MotionEvent.obtain(
@@ -1019,21 +1040,18 @@
0,
0
);
- MotionEvent syntheticDownEvent = upEvent.copy();
- syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(syntheticDownEvent)));
-
- mStackScroller.dispatchTouchEvent(upEvent);
-
- verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
- new MotionEventMatcher(upEvent)));
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
assertFalse(mStackScroller.getIsBeingDragged());
}
+ private MotionEvent captureTouchSentToSceneFramework() {
+ ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class);
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture());
+ return captor.getValue();
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -1081,20 +1099,23 @@
);
}
- private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
- private final MotionEvent mLeftEvent;
+ private MotionEventSubject assertThatMotionEvent(MotionEvent actual) {
+ return new MotionEventSubject(actual);
+ }
- MotionEventMatcher(MotionEvent leftEvent) {
- mLeftEvent = leftEvent;
+ private static class MotionEventSubject {
+ private final MotionEvent mActual;
+
+ MotionEventSubject(MotionEvent actual) {
+ mActual = actual;
}
- @Override
- public boolean matches(MotionEvent right) {
- return mLeftEvent.getActionMasked() == right.getActionMasked()
- && mLeftEvent.getDownTime() == right.getDownTime()
- && mLeftEvent.getEventTime() == right.getEventTime()
- && mLeftEvent.getX() == right.getX()
- && mLeftEvent.getY() == right.getY();
+ public void matches(MotionEvent expected) {
+ assertThat(mActual.getActionMasked()).isEqualTo(expected.getActionMasked());
+ assertThat(mActual.getDownTime()).isEqualTo(expected.getDownTime());
+ assertThat(mActual.getEventTime()).isEqualTo(expected.getEventTime());
+ assertThat(mActual.getX()).isEqualTo(expected.getX());
+ assertThat(mActual.getY()).isEqualTo(expected.getY());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 3666248..562aa6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -86,6 +86,7 @@
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
@@ -94,6 +95,7 @@
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.domain.interactor.StatusBarKeyguardViewManagerInteractor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -224,7 +226,9 @@
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
- mock(JavaAdapter.class)) {
+ mock(JavaAdapter.class),
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -733,7 +737,9 @@
() -> mock(KeyguardDismissActionInteractor.class),
mSelectedUserInteractor,
() -> mock(KeyguardSurfaceBehindInteractor.class),
- mock(JavaAdapter.class)) {
+ mock(JavaAdapter.class),
+ () -> mock(SceneInteractor.class),
+ mock(StatusBarKeyguardViewManagerInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1dafcc4..b0404a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,13 +15,14 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK;
import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index c02583a..ab28a2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -718,7 +718,8 @@
@Test
public void onPrivateProfileAdded_ignoresUntilStartComplete() {
- mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
reset(mDeviceProvisionedController);
when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
mBroadcastReceiver.getValue().onReceive(null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index 3a6324d..d0261ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -69,7 +69,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.media.dialog.MediaOutputDialogManager;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.res.R;
@@ -130,7 +130,7 @@
@Mock
DeviceProvisionedController mDeviceProvisionedController;
@Mock
- MediaOutputDialogFactory mMediaOutputDialogFactory;
+ MediaOutputDialogManager mMediaOutputDialogManager;
@Mock
InteractionJankMonitor mInteractionJankMonitor;
@Mock
@@ -196,7 +196,7 @@
mAccessibilityMgr,
mDeviceProvisionedController,
mConfigurationController,
- mMediaOutputDialogFactory,
+ mMediaOutputDialogManager,
mInteractionJankMonitor,
mVolumePanelNavigationInteractor,
mVolumeNavigator,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
index c3f677e..d5411ad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractorKosmos.kt
@@ -21,6 +21,7 @@
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
@@ -40,5 +41,6 @@
context = mockedContext,
activityStarter = activityStarter,
powerInteractor = powerInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a9a2d91..dcbd577 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.data.repository
import android.annotation.FloatRange
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -65,55 +66,79 @@
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScope: TestScope,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
- sendTransitionSteps(from, to, testScope.testScheduler)
+ sendTransitionSteps(from, to, testScope.testScheduler, throughTransitionState)
}
/**
- * Sends STARTED, RUNNING, and FINISHED TransitionSteps between [from] and [to], calling
- * [runCurrent] after each step.
+ * Sends TransitionSteps between [from] and [to], calling [runCurrent] after each step.
+ *
+ * By default, sends steps through FINISHED (STARTED, RUNNING, FINISHED) but can be halted part
+ * way using [throughTransitionState].
*/
suspend fun sendTransitionSteps(
from: KeyguardState,
to: KeyguardState,
testScheduler: TestCoroutineScheduler,
+ throughTransitionState: TransitionState = TransitionState.FINISHED,
) {
sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.STARTED,
- from = from,
- to = to,
- value = 0f,
- )
+ step =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = from,
+ to = to,
+ value = 0f,
+ )
)
testScheduler.runCurrent()
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.RUNNING,
- from = from,
- to = to,
- value = 0.5f
+ if (
+ throughTransitionState == TransitionState.RUNNING ||
+ throughTransitionState == TransitionState.FINISHED
+ ) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = from,
+ to = to,
+ value = 0.5f
+ )
)
- )
- testScheduler.runCurrent()
+ testScheduler.runCurrent()
+ }
- sendTransitionStep(
- TransitionStep(
- transitionState = TransitionState.FINISHED,
- from = from,
- to = to,
- value = 1f,
+ if (throughTransitionState == TransitionState.FINISHED) {
+ sendTransitionStep(
+ step =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = from,
+ to = to,
+ value = 1f,
+ )
)
+ testScheduler.runCurrent()
+ }
+ }
+
+ suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ this.sendTransitionStep(
+ step = step,
+ validateStep = validateStep,
+ ownerName = step.ownerName
)
- testScheduler.runCurrent()
}
/**
@@ -132,7 +157,22 @@
* If you're testing something involving transitions themselves and are sure you want to send
* only a FINISHED step, override [validateStep].
*/
- suspend fun sendTransitionStep(step: TransitionStep, validateStep: Boolean = true) {
+ suspend fun sendTransitionStep(
+ from: KeyguardState = KeyguardState.OFF,
+ to: KeyguardState = KeyguardState.OFF,
+ value: Float = 0f,
+ transitionState: TransitionState = TransitionState.FINISHED,
+ ownerName: String = "",
+ step: TransitionStep =
+ TransitionStep(
+ from = from,
+ to = to,
+ value = value,
+ transitionState = transitionState,
+ ownerName = ownerName
+ ),
+ validateStep: Boolean = true
+ ) {
_transitions.replayCache.last().let { lastStep ->
if (
validateStep &&
@@ -159,7 +199,9 @@
step: TransitionStep,
validateStep: Boolean = true
): Job {
- return coroutineScope.launch { sendTransitionStep(step, validateStep) }
+ return coroutineScope.launch {
+ sendTransitionStep(step = step, validateStep = validateStep)
+ }
}
suspend fun sendTransitionSteps(
@@ -168,12 +210,13 @@
validateStep: Boolean = true
) {
steps.forEach {
- sendTransitionStep(it, validateStep = validateStep)
+ sendTransitionStep(step = it, validateStep = validateStep)
testScope.testScheduler.runCurrent()
}
}
override fun startTransition(info: TransitionInfo): UUID? {
+ Log.i("TEST", "Start transition: ", Exception())
return if (info.animator == null) UUID.randomUUID() else null
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
index ad8ccb0..4c8bf90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardOcclusionRepositoryKosmos.kt
@@ -14,12 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.keyguard.data.repository
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.keyguardOcclusionRepository by Kosmos.Fixture { KeyguardOcclusionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
index a9d89a3..40131c7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
@@ -19,7 +19,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.content.applicationContext
-import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.doze.util.burnInHelperWrapper
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
@@ -31,7 +31,7 @@
context = applicationContext,
burnInHelperWrapper = burnInHelperWrapper,
scope = applicationCoroutineScope,
- configurationRepository = configurationRepository,
+ configurationInteractor = configurationInteractor,
keyguardInteractor = keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..530cbed
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromAlternateBouncerTransitionInteractor by
+ Kosmos.Fixture {
+ FromAlternateBouncerTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index 2477415..bbe37c1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -18,19 +18,21 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromAodTransitionInteractor by
Kosmos.Fixture {
FromAodTransitionInteractor(
transitionRepository = fakeKeyguardTransitionRepository,
transitionInteractor = keyguardTransitionInteractor,
- scope = testScope,
+ scope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..23dcd96
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDozingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDozingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ communalInteractor = communalInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..f7a9d59
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractorKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingLockscreenHostedTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingLockscreenHostedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..135644c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromDreamingTransitionInteractor by
+ Kosmos.Fixture {
+ FromDreamingTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..1695327
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+var Kosmos.fromGlanceableHubTransitionInteractor by
+ Kosmos.Fixture {
+ FromGlanceableHubTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ glanceableHubTransitions = glanceableHubTransitions,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
index 25fc67a..604d9e4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractorKosmos.kt
@@ -17,11 +17,13 @@
package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
val Kosmos.fromGoneTransitionInteractor by
Kosmos.Fixture {
@@ -34,5 +36,7 @@
keyguardInteractor = keyguardInteractor,
powerInteractor = powerInteractor,
communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ biometricSettingsRepository = biometricSettingsRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 3b52676..162fd90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -23,6 +23,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
var Kosmos.fromLockscreenTransitionInteractor by
Kosmos.Fixture {
@@ -38,5 +39,6 @@
powerInteractor = powerInteractor,
glanceableHubTransitions = glanceableHubTransitions,
swipeToDismissInteractor = swipeToDismissInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
new file mode 100644
index 0000000..fc740a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractorKosmos.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+
+val Kosmos.fromOccludedTransitionInteractor by
+ Kosmos.Fixture {
+ FromOccludedTransitionInteractor(
+ transitionRepository = keyguardTransitionRepository,
+ transitionInteractor = keyguardTransitionInteractor,
+ scope = applicationCoroutineScope,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ keyguardInteractor = keyguardInteractor,
+ powerInteractor = powerInteractor,
+ communalInteractor = communalInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
index 6b76449..98babff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.user.domain.interactor.selectedUserInteractor
var Kosmos.fromPrimaryBouncerTransitionInteractor by
@@ -40,5 +41,6 @@
keyguardSecurityModel = keyguardSecurityModel,
selectedUserInteractor = selectedUserInteractor,
powerInteractor = powerInteractor,
+ keyguardOcclusionInteractor = keyguardOcclusionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
index 6df7493..6cc1e8e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt
@@ -16,19 +16,21 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
-import dagger.Lazy
val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by
Kosmos.Fixture {
KeyguardTransitionInteractor(
scope = applicationCoroutineScope,
repository = keyguardTransitionRepository,
- fromLockscreenTransitionInteractor = Lazy { fromLockscreenTransitionInteractor },
- fromPrimaryBouncerTransitionInteractor =
- Lazy { fromPrimaryBouncerTransitionInteractor },
- fromAodTransitionInteractor = Lazy { fromAodTransitionInteractor },
+ keyguardRepository = keyguardRepository,
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = { fromPrimaryBouncerTransitionInteractor },
+ fromAodTransitionInteractor = { fromAodTransitionInteractor },
+ fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor },
+ fromDozingTransitionInteractor = { fromDozingTransitionInteractor },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
index ad8ccb0..8162520 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModelKosmos.kt
@@ -14,12 +14,16 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToOccludedTransitionViewModel by
+ Kosmos.Fixture {
+ DozingToOccludedTransitionViewModel(
+ animationFlow = keyguardTransitionAnimationFlow,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index d5d357f..a863edf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
@file:OptIn(ExperimentalCoroutinesApi::class)
package com.android.systemui.keyguard.ui.viewmodel
@@ -24,6 +23,7 @@
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.dozeParameters
@@ -32,6 +32,7 @@
val Kosmos.keyguardRootViewModel by Fixture {
KeyguardRootViewModel(
+ scope = applicationCoroutineScope,
deviceEntryInteractor = deviceEntryInteractor,
dozeParameters = dozeParameters,
keyguardInteractor = keyguardInteractor,
@@ -41,8 +42,11 @@
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToGoneTransitionViewModel = dozingToGoneTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
+ dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
glanceableHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
index e1b1966..e788669 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/MediaKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.media
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.dialog.MediaOutputDialogFactory
+import com.android.systemui.media.dialog.MediaOutputDialogManager
import com.android.systemui.util.mockito.mock
-var Kosmos.mediaOutputDialogFactory: MediaOutputDialogFactory by Kosmos.Fixture { mock {} }
+var Kosmos.mediaOutputDialogManager: MediaOutputDialogManager by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
index 0183b97..63e2d4b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/data/repository/FakeSpatializerRepository.kt
@@ -18,23 +18,27 @@
import android.media.AudioDeviceAttributes
import com.android.settingslib.media.data.repository.SpatializerRepository
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
class FakeSpatializerRepository : SpatializerRepository {
var defaultSpatialAudioAvailable: Boolean = false
+ var defaultHeadTrackingAvailable: Boolean = false
private val spatialAudioAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
mutableMapOf()
+ private val headTrackingAvailabilityByDevice: MutableMap<AudioDeviceAttributes, Boolean> =
+ mutableMapOf()
private val spatialAudioCompatibleDevices: MutableList<AudioDeviceAttributes> = mutableListOf()
- private val mutableHeadTrackingAvailable = MutableStateFlow(false)
private val headTrackingEnabledByDevice = mutableMapOf<AudioDeviceAttributes, Boolean>()
- override val isHeadTrackingAvailable: StateFlow<Boolean> =
- mutableHeadTrackingAvailable.asStateFlow()
+ override suspend fun isHeadTrackingAvailableForDevice(
+ audioDeviceAttributes: AudioDeviceAttributes
+ ): Boolean =
+ headTrackingAvailabilityByDevice.getOrDefault(
+ audioDeviceAttributes,
+ defaultHeadTrackingAvailable
+ )
override suspend fun isSpatialAudioAvailableForDevice(
audioDeviceAttributes: AudioDeviceAttributes
@@ -77,7 +81,10 @@
spatialAudioAvailabilityByDevice[audioDeviceAttributes] = isAvailable
}
- fun setIsHeadTrackingAvailable(isAvailable: Boolean) {
- mutableHeadTrackingAvailable.value = isAvailable
+ fun setIsHeadTrackingAvailable(
+ audioDeviceAttributes: AudioDeviceAttributes,
+ isAvailable: Boolean,
+ ) {
+ headTrackingAvailabilityByDevice[audioDeviceAttributes] = isAvailable
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
index 920e5ee..41d2d60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeActivityTaskManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeActivityTaskManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.app.ActivityManager.RunningTaskInfo
import android.app.IActivityTaskManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
index 28393e8..2b6032c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/FakeMediaProjectionManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.mediaprojection.taskswitcher.data.repository
+package com.android.systemui.mediaprojection.taskswitcher
import android.media.projection.MediaProjectionInfo
import android.media.projection.MediaProjectionManager
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
new file mode 100644
index 0000000..d344b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/mediaprojection/taskswitcher/TaskSwitcherKosmos.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.taskswitcher
+
+import android.os.Handler
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.ActivityTaskManagerTasksRepository
+import com.android.systemui.mediaprojection.taskswitcher.data.repository.MediaProjectionManagerRepository
+import com.android.systemui.mediaprojection.taskswitcher.domain.interactor.TaskSwitchInteractor
+import com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel.TaskSwitcherNotificationViewModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+
+val Kosmos.fakeActivityTaskManager by Kosmos.Fixture { FakeActivityTaskManager() }
+
+val Kosmos.fakeMediaProjectionManager by Kosmos.Fixture { FakeMediaProjectionManager() }
+
+val Kosmos.activityTaskManagerTasksRepository by
+ Kosmos.Fixture {
+ ActivityTaskManagerTasksRepository(
+ activityTaskManager = fakeActivityTaskManager.activityTaskManager,
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher
+ )
+ }
+
+val Kosmos.mediaProjectionManagerRepository by
+ Kosmos.Fixture {
+ MediaProjectionManagerRepository(
+ mediaProjectionManager = fakeMediaProjectionManager.mediaProjectionManager,
+ handler = Handler.getMain(),
+ applicationScope = applicationCoroutineScope,
+ tasksRepository = activityTaskManagerTasksRepository,
+ backgroundDispatcher = testDispatcher,
+ mediaProjectionServiceHelper = fakeMediaProjectionManager.helper,
+ )
+ }
+
+val Kosmos.taskSwitcherInteractor by
+ Kosmos.Fixture {
+ TaskSwitchInteractor(mediaProjectionManagerRepository, activityTaskManagerTasksRepository)
+ }
+
+val Kosmos.taskSwitcherViewModel by
+ Kosmos.Fixture { TaskSwitcherNotificationViewModel(taskSwitcherInteractor, testDispatcher) }
+
+@OptIn(ExperimentalCoroutinesApi::class)
+fun taskSwitcherKosmos() = Kosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
similarity index 62%
copy from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
index ad8ccb0..a2d1d93 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/battery/BatterySaverTileKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.qs.tiles.impl.battery
+import com.android.systemui.battery.BatterySaverModule
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.qs.qsEventLogger
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.qsBatterySaverTileConfig by
+ Kosmos.Fixture { BatterySaverModule.provideBatterySaverTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
similarity index 62%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
index ad8ccb0..6772ba3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/MediaOutputComponentKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/internet/InternetTileKosmos.kt
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.volume.panel
+package com.android.systemui.qs.tiles.impl.internet
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.statusbar.connectivity.ConnectivityModule
-val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+val Kosmos.qsInternetTileConfig by
+ Kosmos.Fixture { ConnectivityModule.provideInternetTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..d793740
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardOcclusionInteractorKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardOcclusionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.keyguardOcclusionInteractor by
+ Kosmos.Fixture {
+ KeyguardOcclusionInteractor(
+ scope = testScope.backgroundScope,
+ repository = keyguardOcclusionRepository,
+ powerInteractor = powerInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
new file mode 100644
index 0000000..9e34fe8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardViewOcclusionInteractorKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.statusBarKeyguardViewManagerInteractor by
+ Kosmos.Fixture {
+ StatusBarKeyguardViewManagerInteractor(
+ keyguardTransitionInteractor = this.keyguardTransitionInteractor,
+ keyguardOcclusionInteractor = this.keyguardOcclusionInteractor,
+ powerInteractor = this.powerInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
new file mode 100644
index 0000000..7ffa262
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/NotificationViewFlipperViewModelKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.ui.viewmodel
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
+
+val Kosmos.notificationViewFlipperViewModel by Fixture {
+ NotificationViewFlipperViewModel(
+ dumpManager = dumpManager,
+ stackInteractor = notificationStackInteractor,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index 106e85c..c013664 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -23,7 +23,9 @@
import com.android.systemui.keyguard.ui.viewmodel.alternateBouncerToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.goneToAodTransitionViewModel
@@ -57,7 +59,9 @@
communalInteractor = communalInteractor,
alternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
+ aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
+ dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
goneToAodTransitionViewModel = goneToAodTransitionViewModel,
goneToDozingTransitionViewModel = goneToDozingTransitionViewModel,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
index 5ae033c..d798b3b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -30,6 +30,8 @@
private boolean mIsAodPowerSave = false;
private boolean mWirelessCharging;
private boolean mPowerSaveMode = false;
+ private boolean mIsPluggedIn = false;
+ private boolean mIsExtremePowerSave = false;
private final List<BatteryStateChangeCallback> mCallbacks = new ArrayList<>();
@@ -64,8 +66,35 @@
}
@Override
+ public boolean isExtremeSaverOn() {
+ return mIsExtremePowerSave;
+ }
+
+ /**
+ * Note: this does not affect the regular power saver. Triggers all callbacks, only on change.
+ */
+ public void setExtremeSaverOn(Boolean extremePowerSave) {
+ if (extremePowerSave == mIsExtremePowerSave) return;
+
+ mIsExtremePowerSave = extremePowerSave;
+ for (BatteryStateChangeCallback callback: mCallbacks) {
+ callback.onExtremeBatterySaverChanged(extremePowerSave);
+ }
+ }
+
+ @Override
public boolean isPluggedIn() {
- return false;
+ return mIsPluggedIn;
+ }
+
+ /**
+ * Notifies all registered callbacks
+ */
+ public void setPluggedIn(boolean pluggedIn) {
+ mIsPluggedIn = pluggedIn;
+ for (BatteryStateChangeCallback cb : mCallbacks) {
+ cb.onBatteryLevelChanged(0, pluggedIn, false);
+ }
}
@Override
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 3f20df3..3938f77 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -24,8 +24,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.mediaOutputDialogFactory
-import com.android.systemui.plugins.activityStarter
+import com.android.systemui.media.mediaOutputDialogManager
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -43,7 +42,7 @@
Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } }
val Kosmos.mediaOutputActionsInteractor by
- Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogFactory, activityStarter) }
+ Kosmos.Fixture { MediaOutputActionsInteractor(mediaOutputDialogManager) }
val Kosmos.mediaControllerRepository by Kosmos.Fixture { FakeMediaControllerRepository() }
val Kosmos.mediaOutputInteractor by
Kosmos.Fixture {
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index f31eb44..23e269a 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -39,7 +39,9 @@
import android.content.SharedPreferences;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -49,16 +51,22 @@
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
+import android.view.Display;
+import android.view.DisplayInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -102,6 +110,9 @@
@VisibleForTesting
static final String WALLPAPER_INFO_STAGE = "wallpaper-info-stage";
+ @VisibleForTesting
+ static final String WALLPAPER_BACKUP_DEVICE_INFO_STAGE = "wallpaper-backup-device-info-stage";
+
static final String EMPTY_SENTINEL = "empty";
static final String QUOTA_SENTINEL = "quota";
@@ -110,6 +121,11 @@
static final String SYSTEM_GENERATION = "system_gen";
static final String LOCK_GENERATION = "lock_gen";
+ /**
+ * An approximate area threshold to compare device dimension similarity
+ */
+ static final int AREA_THRESHOLD = 50; // TODO (b/327637867): determine appropriate threshold
+
// If this file exists, it means we exceeded our quota last time
private File mQuotaFile;
private boolean mQuotaExceeded;
@@ -121,6 +137,8 @@
private boolean mSystemHasLiveComponent;
private boolean mLockHasLiveComponent;
+ private DisplayManager mDisplayManager;
+
@Override
public void onCreate() {
if (DEBUG) {
@@ -137,6 +155,8 @@
mBackupManager = new BackupManager(getBaseContext());
mEventLogger = new WallpaperEventLogger(mBackupManager, /* wallpaperAgent */ this);
+
+ mDisplayManager = getSystemService(DisplayManager.class);
}
@Override
@@ -175,9 +195,11 @@
mSystemHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_SYSTEM) != null;
mLockHasLiveComponent = mWallpaperManager.getWallpaperInfo(FLAG_LOCK) != null;
+ // performing backup of each file based on order of importance
backupWallpaperInfoFile(/* sysOrLockChanged= */ sysChanged || lockChanged, data);
backupSystemWallpaperFile(sharedPrefs, sysChanged, sysGeneration, data);
backupLockWallpaperFileIfItExists(sharedPrefs, lockChanged, lockGeneration, data);
+ backupDeviceInfoFile(data);
} catch (Exception e) {
Slog.e(TAG, "Unable to back up wallpaper", e);
mEventLogger.onBackupException(e);
@@ -191,6 +213,54 @@
}
}
+ /**
+ * This method backs up the device dimension information. The device data will always get
+ * overwritten when triggering a backup
+ */
+ private void backupDeviceInfoFile(FullBackupDataOutput data)
+ throws IOException {
+ final File deviceInfoStage = new File(getFilesDir(), WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
+
+ // save the dimensions of the device with xml formatting
+ Point dimensions = getScreenDimensions();
+ Display smallerDisplay = getSmallerDisplayIfExists();
+ Point secondaryDimensions = smallerDisplay != null ? getRealSize(smallerDisplay) :
+ new Point(0, 0);
+
+ deviceInfoStage.createNewFile();
+ FileOutputStream fstream = new FileOutputStream(deviceInfoStage, false);
+ TypedXmlSerializer out = Xml.resolveSerializer(fstream);
+ out.startDocument(null, true);
+ out.startTag(null, "dimensions");
+
+ out.startTag(null, "width");
+ out.text(String.valueOf(dimensions.x));
+ out.endTag(null, "width");
+
+ out.startTag(null, "height");
+ out.text(String.valueOf(dimensions.y));
+ out.endTag(null, "height");
+
+ if (smallerDisplay != null) {
+ out.startTag(null, "secondarywidth");
+ out.text(String.valueOf(secondaryDimensions.x));
+ out.endTag(null, "secondarywidth");
+
+ out.startTag(null, "secondaryheight");
+ out.text(String.valueOf(secondaryDimensions.y));
+ out.endTag(null, "secondaryheight");
+ }
+
+ out.endTag(null, "dimensions");
+ out.endDocument();
+ fstream.flush();
+ FileUtils.sync(fstream);
+ fstream.close();
+
+ if (DEBUG) Slog.v(TAG, "Storing device dimension data");
+ backupFile(deviceInfoStage, data);
+ }
+
private void backupWallpaperInfoFile(boolean sysOrLockChanged, FullBackupDataOutput data)
throws IOException {
final ParcelFileDescriptor wallpaperInfoFd = mWallpaperManager.getWallpaperInfoFile();
@@ -364,9 +434,22 @@
final File infoStage = new File(filesDir, WALLPAPER_INFO_STAGE);
final File imageStage = new File(filesDir, SYSTEM_WALLPAPER_STAGE);
final File lockImageStage = new File(filesDir, LOCK_WALLPAPER_STAGE);
+ final File deviceDimensionsStage = new File(filesDir, WALLPAPER_BACKUP_DEVICE_INFO_STAGE);
boolean lockImageStageExists = lockImageStage.exists();
try {
+ // Parse the device dimensions of the source device and compare with target to
+ // to identify whether we need to skip the remainder of the restore process
+ Pair<Point, Point> sourceDeviceDimensions = parseDeviceDimensions(
+ deviceDimensionsStage);
+
+ Point targetDeviceDimensions = getScreenDimensions();
+ if (sourceDeviceDimensions != null && targetDeviceDimensions != null
+ && isSourceDeviceSignificantlySmallerThanTarget(sourceDeviceDimensions.first,
+ targetDeviceDimensions)) {
+ Slog.d(TAG, "The source device is significantly smaller than target");
+ }
+
// First parse the live component name so that we know for logging if we care about
// logging errors with the image restore.
ComponentName wpService = parseWallpaperComponent(infoStage, "wp");
@@ -400,6 +483,7 @@
infoStage.delete();
imageStage.delete();
lockImageStage.delete();
+ deviceDimensionsStage.delete();
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit()
@@ -409,6 +493,66 @@
}
}
+ /**
+ * This method parses the given file for the backed up device dimensions
+ *
+ * @param deviceDimensions the file which holds the device dimensions
+ * @return the backed up device dimensions
+ */
+ private Pair<Point, Point> parseDeviceDimensions(File deviceDimensions) {
+ int width = 0, height = 0, secondaryHeight = 0, secondaryWidth = 0;
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(
+ new FileInputStream(deviceDimensions));
+
+ while (parser.next() != XmlPullParser.END_TAG) {
+ if (parser.getEventType() != XmlPullParser.START_TAG) {
+ continue;
+ }
+
+ String name = parser.getName();
+
+ switch (name) {
+ case "width":
+ String widthText = readText(parser);
+ width = Integer.valueOf(widthText);
+ break;
+
+ case "height":
+ String textHeight = readText(parser);
+ height = Integer.valueOf(textHeight);
+ break;
+
+ case "secondarywidth":
+ String secondaryWidthText = readText(parser);
+ secondaryWidth = Integer.valueOf(secondaryWidthText);
+ break;
+
+ case "secondaryheight":
+ String secondaryHeightText = readText(parser);
+ secondaryHeight = Integer.valueOf(secondaryHeightText);
+ break;
+ default:
+ break;
+ }
+ }
+ return new Pair<>(new Point(width, height), new Point(secondaryWidth, secondaryHeight));
+
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private static String readText(TypedXmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ String result = "";
+ if (parser.next() == XmlPullParser.TEXT) {
+ result = parser.getText();
+ parser.nextTag();
+ }
+ return result;
+ }
+
@VisibleForTesting
void updateWallpaperComponent(ComponentName wpService, int which)
throws IOException {
@@ -691,6 +835,94 @@
};
}
+ /**
+ * This method retrieves the dimensions of the largest display of the device
+ *
+ * @return a @{Point} object that contains the dimensions of the largest display on the device
+ */
+ private Point getScreenDimensions() {
+ Point largetDimensions = null;
+ int maxArea = 0;
+
+ for (Display display : getInternalDisplays()) {
+ Point displaySize = getRealSize(display);
+
+ int width = displaySize.x;
+ int height = displaySize.y;
+ int area = width * height;
+
+ if (area > maxArea) {
+ maxArea = area;
+ largetDimensions = displaySize;
+ }
+ }
+
+ return largetDimensions;
+ }
+
+ private Point getRealSize(Display display) {
+ DisplayInfo displayInfo = new DisplayInfo();
+ display.getDisplayInfo(displayInfo);
+ return new Point(displayInfo.logicalWidth, displayInfo.logicalHeight);
+ }
+
+ /**
+ * This method returns the smaller display on a multi-display device
+ *
+ * @return Display that corresponds to the smaller display on a device or null if ther is only
+ * one Display on a device
+ */
+ private Display getSmallerDisplayIfExists() {
+ List<Display> internalDisplays = getInternalDisplays();
+ Point largestDisplaySize = getScreenDimensions();
+
+ // Find the first non-matching internal display
+ for (Display display : internalDisplays) {
+ Point displaySize = getRealSize(display);
+ if (displaySize.x != largestDisplaySize.x || displaySize.y != largestDisplaySize.y) {
+ return display;
+ }
+ }
+
+ // If no smaller display found, return null, as there is only a single display
+ return null;
+ }
+
+ /**
+ * This method retrieves the collection of Display objects available in the device.
+ * i.e. non-external displays are ignored
+ *
+ * @return list of displays corresponding to each display in the device
+ */
+ private List<Display> getInternalDisplays() {
+ Display[] allDisplays = mDisplayManager.getDisplays(
+ DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED);
+
+ List<Display> internalDisplays = new ArrayList<>();
+ for (Display display : allDisplays) {
+ if (display.getType() == Display.TYPE_INTERNAL) {
+ internalDisplays.add(display);
+ }
+ }
+ return internalDisplays;
+ }
+
+ /**
+ * This method compares the source and target dimensions, and returns true if there is a
+ * significant difference in area between them and the source dimensions are smaller than the
+ * target dimensions.
+ *
+ * @param sourceDimensions is the dimensions of the source device
+ * @param targetDimensions is the dimensions of the target device
+ */
+ @VisibleForTesting
+ boolean isSourceDeviceSignificantlySmallerThanTarget(Point sourceDimensions,
+ Point targetDimensions) {
+ int rawAreaDelta = (targetDimensions.x * targetDimensions.y)
+ - (sourceDimensions.x * sourceDimensions.y);
+ return rawAreaDelta > AREA_THRESHOLD;
+ }
+
@VisibleForTesting
boolean isDeviceInRestore() {
try {
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..ec9223c 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -59,6 +59,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
@@ -840,6 +841,26 @@
testParseCropHints(testMap);
}
+ @Test
+ public void test_sourceDimensionsAreLargerThanTarget() {
+ // source device is larger than target, expecting to get false
+ Point sourceDimensions = new Point(2208, 1840);
+ Point targetDimensions = new Point(1080, 2092);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(false);
+ }
+
+ @Test
+ public void test_sourceDimensionsMuchSmallerThanTarget() {
+ // source device is smaller than target, expecting to get true
+ Point sourceDimensions = new Point(1080, 2092);
+ Point targetDimensions = new Point(2208, 1840);
+ boolean isSourceSmaller = mWallpaperBackupAgent
+ .isSourceDeviceSignificantlySmallerThanTarget(sourceDimensions, targetDimensions);
+ assertThat(isSourceSmaller).isEqualTo(true);
+ }
+
private void testParseCropHints(Map<Integer, Rect> testMap) throws Exception {
assumeTrue(multiCrop());
mockRestoredStaticWallpaperFile(testMap);
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index cc94090..9057d16 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -42,7 +42,6 @@
public static final String KEYBOARD_PATHS = "keyboard_paths";
public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
- public static final String VALUE_N_A = "**n/a**";
public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";
private static String sInitialDir = new File("").getAbsolutePath();
@@ -130,8 +129,6 @@
}
setProperty(CORE_NATIVE_CLASSES, jniClasses);
setProperty(GRAPHICS_NATIVE_CLASSES, "");
- setProperty(ICU_DATA_PATH, VALUE_N_A);
- setProperty(KEYBOARD_PATHS, VALUE_N_A);
RavenwoodUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
}
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a754ba5..997f3af 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -59,6 +59,13 @@
}
flag {
+ name: "enable_magnification_one_finger_panning_gesture"
+ namespace: "accessibility"
+ description: "Whether to allow easy-mode (one finger panning gesture) for magnification"
+ bug: "282039824"
+}
+
+flag {
name: "fix_drag_pointer_when_ending_drag"
namespace: "accessibility"
description: "Send the correct pointer id when transitioning from dragging to delegating states."
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index f4ea754..6d1ab9f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -167,7 +167,7 @@
})
public @interface OverscrollState {}
- @VisibleForTesting boolean mIsSinglePanningEnabled;
+ @VisibleForTesting final OneFingerPanningSettingsProvider mOneFingerPanningSettingsProvider;
private final FullScreenMagnificationVibrationHelper mFullScreenMagnificationVibrationHelper;
@@ -201,7 +201,11 @@
displayId,
fullScreenMagnificationVibrationHelper,
/* magnificationLogger= */ null,
- ViewConfiguration.get(context));
+ ViewConfiguration.get(context),
+ new OneFingerPanningSettingsProvider(
+ context,
+ Flags.enableMagnificationOneFingerPanningGesture()
+ ));
}
/** Constructor for tests. */
@@ -218,7 +222,9 @@
int displayId,
FullScreenMagnificationVibrationHelper fullScreenMagnificationVibrationHelper,
MagnificationLogger magnificationLogger,
- ViewConfiguration viewConfiguration) {
+ ViewConfiguration viewConfiguration,
+ OneFingerPanningSettingsProvider oneFingerPanningSettingsProvider
+ ) {
super(displayId, detectSingleFingerTripleTap, detectTwoFingerTripleTap,
detectShortcutTrigger, trace, callback);
if (DEBUG_ALL) {
@@ -301,9 +307,7 @@
mPanningScalingState = new PanningScalingState(context);
mSinglePanningState = new SinglePanningState(context);
mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
- setSinglePanningEnabled(
- context.getResources()
- .getBoolean(R.bool.config_enable_a11y_magnification_single_panning));
+ mOneFingerPanningSettingsProvider = oneFingerPanningSettingsProvider;
mOverscrollHandler = new OverscrollHandler();
mIsWatch = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
@@ -317,11 +321,6 @@
transitionTo(mDetectingState);
}
- @VisibleForTesting
- void setSinglePanningEnabled(boolean isEnabled) {
- mIsSinglePanningEnabled = isEnabled;
- }
-
@Override
void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
if (event.getActionMasked() == ACTION_DOWN) {
@@ -361,6 +360,7 @@
Slog.i(mLogTag, "onDestroy(); delayed = "
+ MotionEventInfo.toString(mDetectingState.mDelayedEventQueue));
}
+ mOneFingerPanningSettingsProvider.unregister();
if (mScreenStateReceiver != null) {
mScreenStateReceiver.unregister();
@@ -524,7 +524,7 @@
&& event.getPointerCount() == 2 // includes the pointer currently being released
&& mPreviousState == mViewportDraggingState) {
// if feature flag is enabled, currently only true on watches
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
}
@@ -532,7 +532,7 @@
} else if (action == ACTION_UP || action == ACTION_CANCEL) {
onPanningFinished(event);
// if feature flag is enabled, currently only true on watches
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.setScaleAndCenterToEdgeIfNeeded();
mOverscrollHandler.clearEdgeState();
}
@@ -611,7 +611,7 @@
onPan(second);
mFullScreenMagnificationController.offsetMagnifiedRegion(mDisplayId, distanceX,
distanceY, AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
- if (mIsSinglePanningEnabled) {
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()) {
mOverscrollHandler.onScrollStateChanged(first, second);
}
return /* event consumed: */ true;
@@ -1000,21 +1000,23 @@
&& event.getPointerCount() == 2) {
transitionToViewportDraggingStateAndClear(event);
} else if (isActivated() && event.getPointerCount() == 2) {
- if (mIsSinglePanningEnabled
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- } // TODO(b/319537921): should there be an else here?
- //Primary pointer is swiping, so transit to PanningScalingState
- transitToPanningScalingStateAndClear();
- } else if (mIsSinglePanningEnabled
+ } else {
+ //Primary pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
+ }
+ } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
- } // TODO(b/319537921): should there be an else here?
- transitToSinglePanningStateAndClear();
+ } else {
+ transitToSinglePanningStateAndClear();
+ }
} else if (!mIsTwoFingerCountReached) {
// If it is a two-finger gesture, do not transition to the
// delegating state to ensure the reachability of
@@ -1253,21 +1255,23 @@
if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
transitionToViewportDraggingStateAndClear(event);
} else if (isActivated() && event.getPointerCount() == 2) {
- if (mIsSinglePanningEnabled
+ if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
+ } else {
+ //Primary pointer is swiping, so transit to PanningScalingState
+ transitToPanningScalingStateAndClear();
}
- //Primary pointer is swiping, so transit to PanningScalingState
- transitToPanningScalingStateAndClear();
- } else if (mIsSinglePanningEnabled
+ } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
&& isActivated()
&& event.getPointerCount() == 1) {
if (overscrollState(event, mFirstPointerDownLocation)
== OVERSCROLL_VERTICAL_EDGE) {
transitionToDelegatingStateAndClear();
+ } else {
+ transitToSinglePanningStateAndClear();
}
- transitToSinglePanningStateAndClear();
} else {
transitionToDelegatingStateAndClear();
}
@@ -1629,7 +1633,8 @@
+ ", mPreviousState=" + State.nameOf(mPreviousState)
+ ", mMagnificationController=" + mFullScreenMagnificationController
+ ", mDisplayId=" + mDisplayId
- + ", mIsSinglePanningEnabled=" + mIsSinglePanningEnabled
+ + ", mIsSinglePanningEnabled="
+ + mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
+ ", mOverscrollHandler=" + mOverscrollHandler
+ '}';
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
new file mode 100644
index 0000000..3cdaf98
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/OneFingerPanningSettingsProvider.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.accessibility.magnification;
+
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provider for secure settings {@link Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED}.
+ */
+public class OneFingerPanningSettingsProvider {
+
+ @VisibleForTesting
+ static final String KEY = Settings.Secure.ACCESSIBILITY_SINGLE_FINGER_PANNING_ENABLED;
+ private static final Uri URI = Settings.Secure.getUriFor(KEY);
+ private AtomicBoolean mCached = new AtomicBoolean();
+ @VisibleForTesting
+ ContentObserver mObserver;
+ @VisibleForTesting
+ ContentResolver mContentResolver;
+
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ int OFF = 0;
+ int ON = 1;
+ }
+
+ public OneFingerPanningSettingsProvider(
+ Context context,
+ boolean featureFlagEnabled
+ ) {
+ var defaultValue = isOneFingerPanningEnabledDefault(context);
+ if (featureFlagEnabled) {
+ mContentResolver = context.getContentResolver();
+ mObserver = new ContentObserver(context.getMainThreadHandler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+ }
+ };
+ mCached.set(isOneFingerPanningEnabledInSetting(context, defaultValue));
+ mContentResolver.registerContentObserver(URI, false, mObserver);
+ } else {
+ mCached.set(defaultValue);
+ }
+ }
+
+ /** Returns whether one finger panning is enabled.. */
+ public boolean isOneFingerPanningEnabled() {
+ return mCached.get();
+ }
+
+ /** Unregister content observer for listening to secure settings. */
+ public void unregister() {
+ if (mContentResolver != null) {
+ mContentResolver.unregisterContentObserver(mObserver);
+ }
+ mContentResolver = null;
+ }
+
+ private boolean isOneFingerPanningEnabledInSetting(Context context, boolean defaultValue) {
+ return State.ON == Settings.Secure.getIntForUser(
+ mContentResolver,
+ KEY,
+ (defaultValue ? State.ON : State.OFF),
+ context.getUserId());
+ }
+
+ @VisibleForTesting
+ static boolean isOneFingerPanningEnabledDefault(Context context) {
+ boolean oneFingerPanningDefaultValue;
+ try {
+ oneFingerPanningDefaultValue = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enable_a11y_magnification_single_panning);
+ } catch (Resources.NotFoundException e) {
+ oneFingerPanningDefaultValue = false;
+ }
+ return oneFingerPanningDefaultValue;
+ }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 21a02ed2..1749ee3 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -84,6 +84,7 @@
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
@@ -98,6 +99,7 @@
import android.service.appwidget.AppWidgetServiceDumpProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.AttributeSet;
@@ -148,6 +150,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -159,6 +162,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.LongSupplier;
class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider,
OnCrossProfileWidgetProvidersChangeListener {
@@ -187,6 +191,13 @@
// used to verify which request has successfully been received by the host.
private static final AtomicLong UPDATE_COUNTER = new AtomicLong();
+ // Default reset interval for generated preview API rate limiting.
+ private static final long DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS =
+ Duration.ofHours(1).toMillis();
+ // Default max API calls per reset interval for generated preview API rate limiting.
+ private static final int DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL = 2;
+
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -266,6 +277,8 @@
// Mark widget lifecycle broadcasts as 'interactive'
private Bundle mInteractiveBroadcast;
+ private ApiCounter mGeneratedPreviewsApiCounter;
+
AppWidgetServiceImpl(Context context) {
mContext = context;
}
@@ -294,6 +307,17 @@
mIsCombinedBroadcastEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.COMBINED_BROADCAST_ENABLED, true);
+ final long generatedPreviewResetInterval = DeviceConfig.getLong(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_RESET_INTERVAL_MS);
+ final int generatedPreviewMaxCallsPerInterval = DeviceConfig.getInt(NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ DEFAULT_GENERATED_PREVIEW_MAX_CALLS_PER_INTERVAL);
+ mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
+ generatedPreviewMaxCallsPerInterval);
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
+ new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
+
BroadcastOptions opts = BroadcastOptions.makeBasic();
opts.setBackgroundActivityStartsAllowed(false);
opts.setInteractive(true);
@@ -2078,6 +2102,7 @@
private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, int viewId, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget view data changed");
callbacks.viewDataChanged(appWidgetId, viewId);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2162,6 +2187,9 @@
private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks,
int appWidgetId, RemoteViews views, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget update for package "
+ + (views == null ? "null" : views.getPackage())
+ + " with widget id: " + appWidgetId);
callbacks.updateAppWidget(appWidgetId, views);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2200,6 +2228,7 @@
private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks,
int appWidgetId, AppWidgetProviderInfo info, long requestId) {
try {
+ Slog.d(TAG, "Trying to notify provider update");
callbacks.providerChanged(appWidgetId, info);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2243,6 +2272,7 @@
private void handleNotifyAppWidgetRemoved(Host host, IAppWidgetHost callbacks, int appWidgetId,
long requestId) {
try {
+ Slog.d(TAG, "Trying to notify widget removed");
callbacks.appWidgetRemoved(appWidgetId);
host.lastWidgetUpdateSequenceNo = requestId;
} catch (RemoteException re) {
@@ -2290,6 +2320,7 @@
private void handleNotifyProvidersChanged(Host host, IAppWidgetHost callbacks) {
try {
+ Slog.d(TAG, "Trying to notify widget providers changed");
callbacks.providersChanged();
} catch (RemoteException re) {
synchronized (mLock) {
@@ -2419,7 +2450,8 @@
AppWidgetProviderInfo info = createPartialProviderInfo(providerId, ri, existing);
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()) {
+ && android.multiuser.Flags.disablePrivateSpaceItemsOnHome()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Do not add widget providers for profiles with items restricted on home screen.
if (info != null && mUserManager
.getUserProperties(info.getProfile()).areItemsRestrictedOnHomeScreen()) {
@@ -2473,6 +2505,7 @@
private void deleteProviderLocked(Provider provider) {
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
+ mGeneratedPreviewsApiCounter.remove(provider.id);
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -3997,7 +4030,7 @@
}
@Override
- public void setWidgetPreview(@NonNull ComponentName providerComponent,
+ public boolean setWidgetPreview(@NonNull ComponentName providerComponent,
@AppWidgetProviderInfo.CategoryFlags int widgetCategories,
@NonNull RemoteViews preview) {
final int userId = UserHandle.getCallingUserId();
@@ -4019,8 +4052,12 @@
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ return true;
+ }
+ return false;
}
}
@@ -4061,6 +4098,26 @@
}
}
+ private void handleSystemUiDeviceConfigChange(DeviceConfig.Properties properties) {
+ Set<String> changed = properties.getKeyset();
+ synchronized (mLock) {
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS)) {
+ long resetIntervalMs = properties.getLong(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_RESET_INTERVAL_MS,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getResetIntervalMs());
+ mGeneratedPreviewsApiCounter.setResetIntervalMs(resetIntervalMs);
+ }
+ if (changed.contains(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL)) {
+ int maxCallsPerInterval = properties.getInt(
+ SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_CALLS_PER_INTERVAL,
+ /* defaultValue= */ mGeneratedPreviewsApiCounter.getMaxCallsPerInterval());
+ mGeneratedPreviewsApiCounter.setMaxCallsPerInterval(maxCallsPerInterval);
+ }
+ }
+ }
+
private final class CallbackHandler extends Handler {
public static final int MSG_NOTIFY_UPDATE_APP_WIDGET = 1;
public static final int MSG_NOTIFY_PROVIDER_CHANGED = 2;
@@ -4534,11 +4591,11 @@
}
}
- private static final class ProviderId {
+ static final class ProviderId {
final int uid;
final ComponentName componentName;
- private ProviderId(int uid, ComponentName componentName) {
+ ProviderId(int uid, ComponentName componentName) {
this.uid = uid;
this.componentName = componentName;
}
@@ -4781,6 +4838,96 @@
}
}
+ /**
+ * This class keeps track of API calls and implements rate limiting. One instance of this class
+ * tracks calls from all providers for one API, or a group of APIs that should share the same
+ * rate limit.
+ */
+ static final class ApiCounter {
+
+ private static final class ApiCallRecord {
+ // Number of times the API has been called for this provider.
+ public int apiCallCount = 0;
+ // The last time (from SystemClock.elapsedRealtime) the api call count was reset.
+ public long lastResetTimeMs = 0;
+
+ void reset(long nowMs) {
+ apiCallCount = 0;
+ lastResetTimeMs = nowMs;
+ }
+ }
+
+ private final Map<ProviderId, ApiCallRecord> mCallCount = new ArrayMap<>();
+ // The interval at which the call count is reset.
+ private long mResetIntervalMs;
+ // The max number of API calls per interval.
+ private int mMaxCallsPerInterval;
+ // Returns the current time (monotonic). By default this is SystemClock.elapsedRealtime.
+ private LongSupplier mMonotonicClock;
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval) {
+ this(resetIntervalMs, maxCallsPerInterval, SystemClock::elapsedRealtime);
+ }
+
+ ApiCounter(long resetIntervalMs, int maxCallsPerInterval,
+ LongSupplier monotonicClock) {
+ mResetIntervalMs = resetIntervalMs;
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ mMonotonicClock = monotonicClock;
+ }
+
+ public void setResetIntervalMs(long resetIntervalMs) {
+ mResetIntervalMs = resetIntervalMs;
+ }
+
+ public long getResetIntervalMs() {
+ return mResetIntervalMs;
+ }
+
+ public void setMaxCallsPerInterval(int maxCallsPerInterval) {
+ mMaxCallsPerInterval = maxCallsPerInterval;
+ }
+
+ public int getMaxCallsPerInterval() {
+ return mMaxCallsPerInterval;
+ }
+
+ /**
+ * Returns true if the API call for the provider should be allowed, false if it should be
+ * rate-limited.
+ */
+ public boolean tryApiCall(@NonNull ProviderId provider) {
+ final ApiCallRecord record = getOrCreateRecord(provider);
+ final long now = mMonotonicClock.getAsLong();
+ final long timeSinceLastResetMs = now - record.lastResetTimeMs;
+ // If the last reset was beyond the reset interval, reset now.
+ if (timeSinceLastResetMs > mResetIntervalMs) {
+ record.reset(now);
+ }
+ if (record.apiCallCount < mMaxCallsPerInterval) {
+ record.apiCallCount++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Remove the provider's call record from this counter, when the provider is no longer
+ * tracked.
+ */
+ public void remove(@NonNull ProviderId id) {
+ mCallCount.remove(id);
+ }
+
+ @NonNull
+ private ApiCallRecord getOrCreateRecord(@NonNull ProviderId provider) {
+ if (!mCallCount.containsKey(provider)) {
+ mCallCount.put(provider, new ApiCallRecord());
+ }
+ return mCallCount.get(provider);
+ }
+ }
+
private class LoadedWidgetState {
final Widget widget;
final int hostTag;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 551297b..7afb780 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -3935,6 +3935,24 @@
*/
@GuardedBy("mLock")
@Nullable
+ private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) {
+ final int numContexts = mContexts.size();
+ for (int i = numContexts - 1; i >= 0; i--) {
+ final FillContext context = mContexts.get(i);
+ final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(),
+ autofillId);
+ if (node != null) {
+ return node;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Gets the latest non-empty value for the given id in the autofill contexts.
+ */
+ @GuardedBy("mLock")
+ @Nullable
private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) {
final int numContexts = mContexts.size();
for (int i = numContexts - 1; i >= 0; i--) {
@@ -6417,7 +6435,21 @@
mClient.onGetCredentialException(id, viewId, exception.getType(),
exception.getMessage());
} else if (response != null) {
- mClient.onGetCredentialResponse(id, viewId, response);
+ if (viewId.isVirtualInt()) {
+ ViewNode viewNode = getViewNodeFromContextsLocked(viewId);
+ if (viewNode != null && viewNode.getCredentialManagerCallback() != null) {
+ Bundle resultData = new Bundle();
+ resultData.putParcelable(
+ CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+ response);
+ viewNode.getCredentialManagerCallback().send(SUCCESS_CREDMAN_SELECTOR,
+ resultData);
+ } else {
+ Slog.w(TAG, "View node not found after GetCredentialResponse");
+ }
+ } else {
+ mClient.onGetCredentialResponse(id, viewId, response);
+ }
} else {
Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response"
+ "and exception");
diff --git a/services/companion/java/com/android/server/companion/AssociationStore.java b/services/companion/java/com/android/server/companion/AssociationStore.java
deleted file mode 100644
index 01905bb..0000000
--- a/services/companion/java/com/android/server/companion/AssociationStore.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.companion;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.companion.AssociationInfo;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Interface for a store of {@link AssociationInfo}-s.
- */
-public interface AssociationStore {
-
- @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
- CHANGE_TYPE_ADDED,
- CHANGE_TYPE_REMOVED,
- CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
- CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface ChangeType {}
-
- int CHANGE_TYPE_ADDED = 0;
- int CHANGE_TYPE_REMOVED = 1;
- int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
- int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
-
- /** Listener for any changes to {@link AssociationInfo}-s. */
- interface OnChangeListener {
- default void onAssociationChanged(
- @ChangeType int changeType, AssociationInfo association) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- onAssociationAdded(association);
- break;
-
- case CHANGE_TYPE_REMOVED:
- onAssociationRemoved(association);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- onAssociationUpdated(association, true);
- break;
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- onAssociationUpdated(association, false);
- break;
- }
- }
-
- default void onAssociationAdded(AssociationInfo association) {}
-
- default void onAssociationRemoved(AssociationInfo association) {}
-
- default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
- }
-
- /**
- * @return all CDM associations.
- */
- @NonNull
- Collection<AssociationInfo> getAssociations();
-
- /**
- * @return a {@link List} of associations that belong to the user.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId);
-
- /**
- * @return a {@link List} of association that belong to the package.
- */
- @NonNull
- List<AssociationInfo> getAssociationsForPackage(
- @UserIdInt int userId, @NonNull String packageName);
-
- /**
- * @return an association with the given address that belong to the given package if such an
- * association exists, otherwise {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationsForPackageWithAddress(
- @UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress);
-
- /**
- * @return an association with the given id if such an association exists, otherwise
- * {@code null}.
- */
- @Nullable
- AssociationInfo getAssociationById(int id);
-
- /**
- * @return all associations with the given MAc address.
- */
- @NonNull
- List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress);
-
- /** Register a {@link OnChangeListener} */
- void registerListener(@NonNull OnChangeListener listener);
-
- /** Un-register a previously registered {@link OnChangeListener} */
- void unregisterListener(@NonNull OnChangeListener listener);
-
- /** @hide */
- static String changeTypeToString(@ChangeType int changeType) {
- switch (changeType) {
- case CHANGE_TYPE_ADDED:
- return "ASSOCIATION_ADDED";
-
- case CHANGE_TYPE_REMOVED:
- return "ASSOCIATION_REMOVED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
- return "ASSOCIATION_UPDATED";
-
- case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
- return "ASSOCIATION_UPDATED_ADDRESS_UNCHANGED";
-
- default:
- return "Unknown (" + changeType + ")";
- }
- }
-}
diff --git a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
index e4cc1f8..f2409fb 100644
--- a/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
+++ b/services/companion/java/com/android/server/companion/BackupRestoreProcessor.java
@@ -34,6 +34,9 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import java.nio.ByteBuffer;
@@ -54,9 +57,9 @@
@NonNull
private final PackageManagerInternal mPackageManager;
@NonNull
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
@NonNull
- private final PersistentDataStore mPersistentStore;
+ private final AssociationDiskStore mPersistentStore;
@NonNull
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@NonNull
@@ -71,8 +74,8 @@
new PerUserAssociationSet();
BackupRestoreProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
- @NonNull PersistentDataStore persistentStore,
+ @NonNull AssociationStore associationStore,
+ @NonNull AssociationDiskStore persistentStore,
@NonNull SystemDataTransferRequestStore systemDataTransferRequestStore,
@NonNull AssociationRequestsProcessor associationRequestsProcessor) {
mService = service;
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 559ebbc..c801489 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -37,6 +37,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import com.android.server.companion.presence.ObservableUuid;
import com.android.server.companion.presence.ObservableUuidStore;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a478a3d..e4a1048 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -37,7 +37,9 @@
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static com.android.server.companion.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.association.AssociationStore.CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
@@ -117,6 +119,11 @@
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.association.AssociationDiskStore;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.InactiveAssociationsRemovalService;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
@@ -147,8 +154,6 @@
static final String TAG = "CDM_CompanionDeviceManagerService";
static final boolean DEBUG = false;
- /** Range of Association IDs allocated for a user. */
- private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
@@ -160,10 +165,10 @@
private static final int MAX_CN_LENGTH = 500;
private final ActivityManager mActivityManager;
- private PersistentDataStore mPersistentStore;
+ private AssociationDiskStore mAssociationDiskStore;
private final PersistUserStateHandler mUserPersistenceHandler;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private AssociationRequestsProcessor mAssociationRequestsProcessor;
private SystemDataTransferProcessor mSystemDataTransferProcessor;
@@ -178,7 +183,7 @@
private final IAppOpsService mAppOpsManager;
private final PowerWhitelistManager mPowerWhitelistManager;
private final UserManager mUserManager;
- final PackageManagerInternal mPackageManagerInternal;
+ public final PackageManagerInternal mPackageManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
/**
@@ -210,7 +215,7 @@
mUserManager = context.getSystemService(UserManager.class);
mUserPersistenceHandler = new PersistUserStateHandler();
- mAssociationStore = new AssociationStoreImpl();
+ mAssociationStore = new AssociationStore();
mSystemDataTransferRequestStore = new SystemDataTransferRequestStore();
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
@@ -221,15 +226,13 @@
public void onStart() {
final Context context = getContext();
- mPersistentStore = new PersistentDataStore();
+ mAssociationDiskStore = new AssociationDiskStore();
mAssociationRequestsProcessor = new AssociationRequestsProcessor(
/* cdmService */ this, mAssociationStore);
mBackupRestoreProcessor = new BackupRestoreProcessor(
- /* cdmService */ this, mAssociationStore, mPersistentStore,
+ /* cdmService */ this, mAssociationStore, mAssociationDiskStore,
mSystemDataTransferRequestStore, mAssociationRequestsProcessor);
- loadAssociationsFromDisk();
-
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
mAssociationStore.registerListener(mAssociationStoreChangeListener);
@@ -240,13 +243,18 @@
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mObservableUuidStore, mDevicePresenceMonitor,
mPowerManagerInternal);
+
+ mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
+ mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
+ mSystemDataTransferRequestStore);
+
+ loadAssociationsFromDisk();
+
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
mPackageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
- mAssociationRevokeProcessor = new AssociationRevokeProcessor(this, mAssociationStore,
- mPackageManagerInternal, mDevicePresenceMonitor, mCompanionAppController,
- mSystemDataTransferRequestStore);
+
// TODO(b/279663946): move context sync to a dedicated system service
mCrossDeviceSyncController = new CrossDeviceSyncController(getContext(), mTransportManager);
@@ -261,10 +269,13 @@
void loadAssociationsFromDisk() {
final Set<AssociationInfo> allAssociations = new ArraySet<>();
synchronized (mPreviouslyUsedIds) {
+ List<Integer> userIds = new ArrayList<>();
+ for (UserInfo user : mUserManager.getAliveUsers()) {
+ userIds.add(user.id);
+ }
// The data is stored in DE directories, so we can read the data for all users now
// (which would not be possible if the data was stored to CE directories).
- mPersistentStore.readStateForUsers(
- mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
+ mAssociationDiskStore.readStateForUsers(userIds, allAssociations, mPreviouslyUsedIds);
}
final Set<AssociationInfo> activeAssociations =
@@ -288,7 +299,7 @@
}
}
- mAssociationStore.setAssociations(activeAssociations);
+ mAssociationStore.setAssociationsToCache(activeAssociations);
// IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
// persistStateForUser() queries AssociationStore.
@@ -579,7 +590,7 @@
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
+ mAssociationDiskStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -643,7 +654,8 @@
final List<AssociationInfo> associationsForPackage =
mAssociationStore.getAssociationsForPackage(userId, packageName);
for (AssociationInfo association : associationsForPackage) {
- updateSpecialAccessPermissionForAssociatedPackage(association);
+ updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
}
mCompanionAppController.onPackagesChanged(userId);
@@ -689,7 +701,7 @@
}
}
- class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
+ public class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
@@ -1335,7 +1347,10 @@
return usedIdsForPackage;
}
- int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
+ /**
+ * Get a new association id for the package.
+ */
+ public int getNewAssociationIdForPackage(@UserIdInt int userId, @NonNull String packageName) {
synchronized (mPreviouslyUsedIds) {
// First: collect all IDs currently in use for this user's Associations.
final SparseBooleanArray usedIds = new SparseBooleanArray();
@@ -1380,9 +1395,12 @@
}
}
- void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
+ /**
+ * Update special access for the association's package
+ */
+ public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
final PackageInfo packageInfo =
- getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
+ getPackageInfo(getContext(), userId, packageName);
Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
}
@@ -1536,15 +1554,6 @@
}
};
- static int getFirstAssociationIdForUser(@UserIdInt int userId) {
- // We want the IDs to start from 1, not 0.
- return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
- }
-
- static int getLastAssociationIdForUser(@UserIdInt int userId) {
- return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
- }
-
private static Map<String, Set<Integer>> deepUnmodifiableCopy(Map<String, Set<Integer>> orig) {
final Map<String, Set<Integer>> copy = new HashMap<>();
@@ -1668,11 +1677,17 @@
}
}
- void postPersistUserState(@UserIdInt int userId) {
+ /**
+ * Persist associations
+ */
+ public void postPersistUserState(@UserIdInt int userId) {
mUserPersistenceHandler.postPersistUserState(userId);
}
- static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ /**
+ * Set to store associations
+ */
+ public static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
@Override
protected @NonNull Set<AssociationInfo> create(int userId) {
return new ArraySet<>();
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 74b4cab..16877dc 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -32,6 +32,9 @@
import android.util.Base64;
import android.util.proto.ProtoOutputStream;
+import com.android.server.companion.association.AssociationRequestsProcessor;
+import com.android.server.companion.association.AssociationRevokeProcessor;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
@@ -47,7 +50,7 @@
private final CompanionDeviceManagerService mService;
private final AssociationRevokeProcessor mRevokeProcessor;
- private final AssociationStoreImpl mAssociationStore;
+ private final AssociationStore mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final CompanionTransportManager mTransportManager;
@@ -56,7 +59,7 @@
private final BackupRestoreProcessor mBackupRestoreProcessor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
- AssociationStoreImpl associationStore,
+ AssociationStore associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor,
CompanionTransportManager transportManager,
SystemDataTransferProcessor systemDataTransferProcessor,
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
similarity index 92%
rename from services/companion/java/com/android/server/companion/PersistentDataStore.java
rename to services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
index 7527efb..75cb120 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationDiskStore.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static com.android.internal.util.CollectionUtils.forEach;
import static com.android.internal.util.XmlUtils.readBooleanAttribute;
@@ -25,8 +25,8 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
-import static com.android.server.companion.CompanionDeviceManagerService.getFirstAssociationIdForUser;
-import static com.android.server.companion.CompanionDeviceManagerService.getLastAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getFirstAssociationIdForUser;
+import static com.android.server.companion.utils.AssociationUtils.getLastAssociationIdForUser;
import static com.android.server.companion.utils.DataStoreUtils.createStorageFileForUser;
import static com.android.server.companion.utils.DataStoreUtils.fileToByteArray;
import static com.android.server.companion.utils.DataStoreUtils.isEndOfTag;
@@ -38,12 +38,10 @@
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
-import android.content.pm.UserInfo;
import android.net.MacAddress;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.AtomicFile;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -51,7 +49,6 @@
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.companion.utils.DataStoreUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -71,6 +68,8 @@
import java.util.concurrent.ConcurrentMap;
/**
+ * IMPORTANT: This class should NOT be directly used except {@link AssociationStore}
+ *
* The class responsible for persisting Association records and other related information (such as
* previously used IDs) to a disk, and reading the data back from the disk.
*
@@ -107,8 +106,6 @@
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
- *
* <p>
* Since Android T the data is stored using the v1 schema.
*
@@ -161,9 +158,8 @@
* }</pre>
*/
@SuppressLint("LongLogTag")
-final class PersistentDataStore {
- private static final String TAG = "CompanionDevice_PersistentDataStore";
- private static final boolean DEBUG = CompanionDeviceManagerService.DEBUG;
+public final class AssociationDiskStore {
+ private static final String TAG = "CompanionDevice_AssociationDiskStore";
private static final int CURRENT_PERSISTENCE_VERSION = 1;
@@ -200,11 +196,13 @@
private final @NonNull ConcurrentMap<Integer, AtomicFile> mUserIdToStorageFile =
new ConcurrentHashMap<>();
- void readStateForUsers(@NonNull List<UserInfo> users,
+ /**
+ * Read all associations for given users
+ */
+ public void readStateForUsers(@NonNull List<Integer> userIds,
@NonNull Set<AssociationInfo> allAssociationsOut,
@NonNull SparseArray<Map<String, Set<Integer>>> previouslyUsedIdsPerUserOut) {
- for (UserInfo user : users) {
- final int userId = user.id;
+ for (int userId : userIds) {
// Previously used IDs are stored in the "out" collection per-user.
final Map<String, Set<Integer>> previouslyUsedIds = new ArrayMap<>();
@@ -247,12 +245,11 @@
* @param associationsOut a container to read the {@link AssociationInfo}s "into".
* @param previouslyUsedIdsPerPackageOut a container to read the used IDs "into".
*/
- void readStateForUser(@UserIdInt int userId,
+ private void readStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
Slog.i(TAG, "Reading associations for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
@@ -261,12 +258,8 @@
final AtomicFile readFrom;
final String rootTag;
if (!file.getBaseFile().exists()) {
- if (DEBUG) Log.d(TAG, " > File does not exist -> Try to read legacy file");
-
legacyBaseFile = getBaseLegacyStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > Legacy file=" + legacyBaseFile.getPath());
if (!legacyBaseFile.exists()) {
- if (DEBUG) Log.d(TAG, " > Legacy file does not exist -> Abort");
return;
}
@@ -277,27 +270,16 @@
rootTag = XML_TAG_STATE;
}
- if (DEBUG) Log.d(TAG, " > Reading associations...");
final int version = readStateFromFileLocked(userId, readFrom, rootTag,
associationsOut, previouslyUsedIdsPerPackageOut);
- if (DEBUG) {
- Log.d(TAG, " > Done reading: " + associationsOut);
- if (version < CURRENT_PERSISTENCE_VERSION) {
- Log.d(TAG, " > File used old format: v." + version + " -> Re-write");
- }
- }
if (legacyBaseFile != null || version < CURRENT_PERSISTENCE_VERSION) {
// The data is either in the legacy file or in the legacy format, or both.
// Save the data to right file in using the current format.
- if (DEBUG) {
- Log.d(TAG, " > Writing the data to " + file.getBaseFile().getPath());
- }
persistStateToFileLocked(file, associationsOut, previouslyUsedIdsPerPackageOut);
if (legacyBaseFile != null) {
// We saved the data to the right file, can delete the old file now.
- if (DEBUG) Log.d(TAG, " > Deleting legacy file");
legacyBaseFile.delete();
}
}
@@ -314,14 +296,12 @@
* @param associations a set of user's associations.
* @param previouslyUsedIdsPerPackage a set previously used Association IDs for the user.
*/
- void persistStateForUser(@UserIdInt int userId,
+ public void persistStateForUser(@UserIdInt int userId,
@NonNull Collection<AssociationInfo> associations,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackage) {
Slog.i(TAG, "Writing associations for user " + userId + " to disk");
- if (DEBUG) Slog.d(TAG, " > " + associations);
final AtomicFile file = getStorageFileForUser(userId);
- if (DEBUG) Log.d(TAG, " > File=" + file.getBaseFile().getPath());
// getStorageFileForUser() ALWAYS returns the SAME OBJECT, which allows us to synchronize
// accesses to the file on the file system using this AtomicFile object.
synchronized (file) {
@@ -404,7 +384,10 @@
u -> createStorageFileForUser(userId, FILE_NAME));
}
- byte[] getBackupPayload(@UserIdInt int userId) {
+ /**
+ * Get associations backup payload from disk
+ */
+ public byte[] getBackupPayload(@UserIdInt int userId) {
Slog.i(TAG, "Fetching stored state data for user " + userId + " from disk");
final AtomicFile file = getStorageFileForUser(userId);
@@ -413,7 +396,10 @@
}
}
- void readStateFromPayload(byte[] payload, @UserIdInt int userId,
+ /**
+ * Convert payload to a set of associations
+ */
+ public void readStateFromPayload(byte[] payload, @UserIdInt int userId,
@NonNull Set<AssociationInfo> associationsOut,
@NonNull Map<String, Set<Integer>> previouslyUsedIdsPerPackageOut) {
try (ByteArrayInputStream in = new ByteArrayInputStream(payload)) {
@@ -615,7 +601,7 @@
macAddress, displayName, profile, null, selfManaged, notify,
revoked, pending, timeApproved, lastTimeConnected, systemDataSyncFlags);
} catch (Exception e) {
- if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
+ Slog.e(TAG, "Could not create AssociationInfo", e);
}
return associationInfo;
}
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
similarity index 89%
rename from services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 1dab40e..29ec7c2 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
@@ -24,7 +24,6 @@
import static android.content.ComponentName.createRelative;
import static android.content.pm.PackageManager.FEATURE_WATCH;
-import static com.android.server.companion.CompanionDeviceManagerService.DEBUG;
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PermissionsUtils.enforcePermissionForCreatingAssociation;
@@ -59,6 +58,7 @@
import android.util.Slog;
import com.android.internal.R;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.utils.PackageUtils;
import java.util.List;
@@ -107,7 +107,7 @@
* ResultReceiver, MacAddress)
*/
@SuppressLint("LongLogTag")
-class AssociationRequestsProcessor {
+public class AssociationRequestsProcessor {
private static final String TAG = "CDM_AssociationRequestsProcessor";
// AssociationRequestsProcessor <-> UI
@@ -130,12 +130,12 @@
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
private final @NonNull PackageManagerInternal mPackageManager;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
@NonNull
private final ComponentName mCompanionDeviceActivity;
- AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore) {
+ public AssociationRequestsProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore) {
mContext = service.getContext();
mService = service;
mPackageManager = service.mPackageManagerInternal;
@@ -149,7 +149,7 @@
* Handle incoming {@link AssociationRequest}s, sent via
* {@link android.companion.ICompanionDeviceManager#associate(AssociationRequest, IAssociationRequestCallback, String, int)}
*/
- void processNewAssociationRequest(@NonNull AssociationRequest request,
+ public void processNewAssociationRequest(@NonNull AssociationRequest request,
@NonNull String packageName, @UserIdInt int userId,
@NonNull IAssociationRequestCallback callback) {
requireNonNull(request, "Request MUST NOT be null");
@@ -161,11 +161,8 @@
requireNonNull(callback, "Callback MUST NOT be null");
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processNewAssociationRequest() "
- + "request=" + request + ", "
- + "package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")");
- }
+ Slog.d(TAG, "processNewAssociationRequest() " + "request=" + request + ", " + "package=u"
+ + userId + "/" + packageName + " (uid=" + packageUid + ")");
// 1. Enforce permissions and other requirements.
enforcePermissionForCreatingAssociation(mContext, request, packageUid);
@@ -223,7 +220,7 @@
/**
* Process another AssociationRequest in CompanionDeviceActivity to cancel current dialog.
*/
- PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
+ public PendingIntent buildAssociationCancellationIntent(@NonNull String packageName,
@UserIdInt int userId) {
requireNonNull(packageName, "Package name MUST NOT be null");
@@ -248,13 +245,6 @@
final int userId = request.getUserId();
final int packageUid = mPackageManager.getPackageUid(packageName, 0, userId);
- if (DEBUG) {
- Slog.d(TAG, "processAssociationRequestApproval()\n"
- + " package=u" + userId + "/" + packageName + " (uid=" + packageUid + ")\n"
- + " request=" + request + "\n"
- + " macAddress=" + macAddress + "\n");
- }
-
// 1. Need to check permissions again in case something changed, since we first received
// this request.
try {
@@ -288,6 +278,9 @@
}
}
+ /**
+ * Create an association.
+ */
public void createAssociation(@UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
@@ -309,6 +302,9 @@
// that there are other devices with the same profile, so the role holder won't be removed.
}
+ /**
+ * Grant a role if specified and add an association to store.
+ */
public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association,
@Nullable IAssociationRequestCallback callback,
@Nullable ResultReceiver resultReceiver) {
@@ -331,6 +327,9 @@
});
}
+ /**
+ * Enable system data sync.
+ */
public void enableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -338,6 +337,9 @@
mAssociationStore.updateAssociation(updated);
}
+ /**
+ * Disable system data sync.
+ */
public void disableSystemDataSync(int associationId, int flags) {
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -350,7 +352,8 @@
mAssociationStore.addAssociation(association);
- mService.updateSpecialAccessPermissionForAssociatedPackage(association);
+ mService.updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+ association.getPackageName());
logCreateAssociation(association.getDeviceProfile());
}
@@ -431,38 +434,37 @@
private final ResultReceiver mOnRequestConfirmationReceiver =
new ResultReceiver(Handler.getMain()) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle data) {
- if (DEBUG) {
- Slog.d(TAG, "mOnRequestConfirmationReceiver.onReceiveResult() "
- + "code=" + resultCode + ", " + "data=" + data);
- }
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle data) {
+ if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
+ Slog.w(TAG, "Unknown result code:" + resultCode);
+ return;
+ }
- if (resultCode != RESULT_CODE_ASSOCIATION_APPROVED) {
- Slog.w(TAG, "Unknown result code:" + resultCode);
- return;
- }
+ final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST,
+ android.companion.AssociationRequest.class);
+ final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
+ .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
+ final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER,
+ android.os.ResultReceiver.class);
- final AssociationRequest request = data.getParcelable(EXTRA_ASSOCIATION_REQUEST, android.companion.AssociationRequest.class);
- final IAssociationRequestCallback callback = IAssociationRequestCallback.Stub
- .asInterface(data.getBinder(EXTRA_APPLICATION_CALLBACK));
- final ResultReceiver resultReceiver = data.getParcelable(EXTRA_RESULT_RECEIVER, android.os.ResultReceiver.class);
+ requireNonNull(request);
+ requireNonNull(callback);
+ requireNonNull(resultReceiver);
- requireNonNull(request);
- requireNonNull(callback);
- requireNonNull(resultReceiver);
+ final MacAddress macAddress;
+ if (request.isSelfManaged()) {
+ macAddress = null;
+ } else {
+ macAddress = data.getParcelable(EXTRA_MAC_ADDRESS,
+ android.net.MacAddress.class);
+ requireNonNull(macAddress);
+ }
- final MacAddress macAddress;
- if (request.isSelfManaged()) {
- macAddress = null;
- } else {
- macAddress = data.getParcelable(EXTRA_MAC_ADDRESS, android.net.MacAddress.class);
- requireNonNull(macAddress);
- }
-
- processAssociationRequestApproval(request, callback, resultReceiver, macAddress);
- }
- };
+ processAssociationRequestApproval(request, callback, resultReceiver,
+ macAddress);
+ }
+ };
private boolean mayAssociateWithoutPrompt(@NonNull String packageName, @UserIdInt int userId) {
// Throttle frequent associations
diff --git a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
similarity index 95%
rename from services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
rename to services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
index 10963ea..490be0d 100644
--- a/services/companion/java/com/android/server/companion/AssociationRevokeProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRevokeProcessor.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
@@ -38,6 +38,8 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.companion.CompanionApplicationController;
+import com.android.server.companion.CompanionDeviceManagerService;
import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -55,7 +57,7 @@
private static final boolean DEBUG = false;
private final @NonNull Context mContext;
private final @NonNull CompanionDeviceManagerService mService;
- private final @NonNull AssociationStoreImpl mAssociationStore;
+ private final @NonNull AssociationStore mAssociationStore;
private final @NonNull PackageManagerInternal mPackageManagerInternal;
private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
private final @NonNull SystemDataTransferRequestStore mSystemDataTransferRequestStore;
@@ -90,8 +92,8 @@
@GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
- AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
- @NonNull AssociationStoreImpl associationStore,
+ public AssociationRevokeProcessor(@NonNull CompanionDeviceManagerService service,
+ @NonNull AssociationStore associationStore,
@NonNull PackageManagerInternal packageManager,
@NonNull CompanionDevicePresenceMonitor devicePresenceMonitor,
@NonNull CompanionApplicationController applicationController,
@@ -108,8 +110,11 @@
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
}
+ /**
+ * Disassociate an association
+ */
// TODO: also revoke notification access
- void disassociateInternal(int associationId) {
+ public void disassociateInternal(int associationId) {
final AssociationInfo association = mAssociationStore.getAssociationById(associationId);
final int userId = association.getUserId();
final String packageName = association.getPackageName();
@@ -168,7 +173,7 @@
* {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
* which would lead to the poor UX, hence need to try later.
*/
- boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ public boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
final String deviceProfile = association.getDeviceProfile();
@@ -208,15 +213,6 @@
return true;
}
- @SuppressLint("MissingPermission")
- private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
- return Binder.withCleanCallingIdentity(() -> {
- final int uid =
- mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
- return mActivityManager.getUidImportance(uid);
- });
- }
-
/**
* Set revoked flag for active association and add the revoked association and the uid into
* the caches.
@@ -225,7 +221,7 @@
* @see #mUidsPendingRoleHolderRemoval
* @see OnPackageVisibilityChangeListener
*/
- void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ public void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
// First: set revoked flag
association = (new AssociationInfo.Builder(association)).setRevoked(true).build();
final String packageName = association.getPackageName();
@@ -247,6 +243,28 @@
}
/**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ @NonNull
+ public Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
* Remove the revoked association from the cache and also remove the uid from the map if
* there are other associations with the same package still pending for role holder removal.
*
@@ -279,18 +297,6 @@
}
}
- /**
- * @return a copy of the revoked associations set (safeguarding against
- * {@code ConcurrentModificationException}-s).
- */
- @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
- @UserIdInt int userId) {
- synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
- // Return a copy.
- return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
- }
- }
-
private String getPackageNameByUid(int uid) {
synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
return mUidsPendingRoleHolderRemoval.get(uid);
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
similarity index 68%
rename from services/companion/java/com/android/server/companion/AssociationStoreImpl.java
rename to services/companion/java/com/android/server/companion/association/AssociationStore.java
index 8c6ad3b..2f94bde 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -14,15 +14,15 @@
* limitations under the License.
*/
-package com.android.server.companion;
+package com.android.server.companion.association;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.companion.AssociationInfo;
import android.net.MacAddress;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -30,6 +30,8 @@
import com.android.internal.util.CollectionUtils;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -40,24 +42,69 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.StringJoiner;
/**
- * Implementation of the {@link AssociationStore}, with addition of the methods for modification.
- * <ul>
- * <li> {@link #addAssociation(AssociationInfo)}
- * <li> {@link #removeAssociation(int)}
- * <li> {@link #updateAssociation(AssociationInfo)}
- * </ul>
- *
- * The class has package-private access level, and instances of the class should only be created by
- * the {@link CompanionDeviceManagerService}.
- * Other system component (both inside and outside if the com.android.server.companion package)
- * should use public {@link AssociationStore} interface.
+ * Association store for CRUD.
*/
@SuppressLint("LongLogTag")
-class AssociationStoreImpl implements AssociationStore {
- private static final boolean DEBUG = false;
+public class AssociationStore {
+
+ @IntDef(prefix = { "CHANGE_TYPE_" }, value = {
+ CHANGE_TYPE_ADDED,
+ CHANGE_TYPE_REMOVED,
+ CHANGE_TYPE_UPDATED_ADDRESS_CHANGED,
+ CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ChangeType {}
+
+ public static final int CHANGE_TYPE_ADDED = 0;
+ public static final int CHANGE_TYPE_REMOVED = 1;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2;
+ public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3;
+
+ /** Listener for any changes to associations. */
+ public interface OnChangeListener {
+ /**
+ * Called when there are association changes.
+ */
+ default void onAssociationChanged(
+ @AssociationStore.ChangeType int changeType, AssociationInfo association) {
+ switch (changeType) {
+ case CHANGE_TYPE_ADDED:
+ onAssociationAdded(association);
+ break;
+
+ case CHANGE_TYPE_REMOVED:
+ onAssociationRemoved(association);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_CHANGED:
+ onAssociationUpdated(association, true);
+ break;
+
+ case CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED:
+ onAssociationUpdated(association, false);
+ break;
+ }
+ }
+
+ /**
+ * Called when an association is added.
+ */
+ default void onAssociationAdded(AssociationInfo association) {}
+
+ /**
+ * Called when an association is removed.
+ */
+ default void onAssociationRemoved(AssociationInfo association) {}
+
+ /**
+ * Called when an association is updated.
+ */
+ default void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {}
+ }
+
private static final String TAG = "CDM_AssociationStore";
private final Object mLock = new Object();
@@ -72,17 +119,17 @@
@GuardedBy("mListeners")
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
- void addAssociation(@NonNull AssociationInfo association) {
+ /**
+ * Add an association.
+ */
+ public void addAssociation(@NonNull AssociationInfo association) {
+ Slog.i(TAG, "Adding new association=" + association);
+
// Validity check first.
checkNotRevoked(association);
final int id = association.getId();
- if (DEBUG) {
- Log.i(TAG, "addAssociation() " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
-
synchronized (mLock) {
if (mIdMap.containsKey(id)) {
Slog.e(TAG, "Association with id " + id + " already exists.");
@@ -96,34 +143,34 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done adding new association.");
}
broadcastChange(CHANGE_TYPE_ADDED, association);
}
- void updateAssociation(@NonNull AssociationInfo updated) {
+ /**
+ * Update an association.
+ */
+ public void updateAssociation(@NonNull AssociationInfo updated) {
+ Slog.i(TAG, "Updating new association=" + updated);
// Validity check first.
checkNotRevoked(updated);
final int id = updated.getId();
- if (DEBUG) {
- Log.i(TAG, "updateAssociation() " + updated.toShortString());
- Log.d(TAG, " updated=" + updated);
- }
-
final AssociationInfo current;
final boolean macAddressChanged;
synchronized (mLock) {
current = mIdMap.get(id);
if (current == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " does not exist.");
+ Slog.w(TAG, "Can't update association. It does not exist.");
return;
}
- if (DEBUG) Log.d(TAG, " current=" + current);
if (current.equals(updated)) {
- if (DEBUG) Log.w(TAG, " No changes.");
+ Slog.w(TAG, "Association is the same.");
return;
}
@@ -144,6 +191,7 @@
mAddressMap.computeIfAbsent(updatedAddress, it -> new HashSet<>()).add(id);
}
}
+ Slog.i(TAG, "Done updating association.");
}
final int changeType = macAddressChanged ? CHANGE_TYPE_UPDATED_ADDRESS_CHANGED
@@ -151,21 +199,19 @@
broadcastChange(changeType, updated);
}
- void removeAssociation(int id) {
- if (DEBUG) Log.i(TAG, "removeAssociation() id=" + id);
+ /**
+ * Remove an association
+ */
+ public void removeAssociation(int id) {
+ Slog.i(TAG, "Removing association id=" + id);
final AssociationInfo association;
synchronized (mLock) {
association = mIdMap.remove(id);
if (association == null) {
- if (DEBUG) Log.w(TAG, "Association with id " + id + " is not stored.");
+ Slog.w(TAG, "Can't remove association. It does not exist.");
return;
- } else {
- if (DEBUG) {
- Log.i(TAG, "removed " + association.toShortString());
- Log.d(TAG, " association=" + association);
- }
}
final MacAddress macAddress = association.getDeviceMacAddress();
@@ -174,6 +220,8 @@
}
invalidateCacheForUserLocked(association.getUserId());
+
+ Slog.i(TAG, "Done removing association.");
}
broadcastChange(CHANGE_TYPE_REMOVED, association);
@@ -195,12 +243,18 @@
}
}
+ /**
+ * Get associations for the user.
+ */
public @NonNull List<AssociationInfo> getAssociationsForUser(@UserIdInt int userId) {
synchronized (mLock) {
return getAssociationsForUserLocked(userId);
}
}
+ /**
+ * Get associations for the package
+ */
public @NonNull List<AssociationInfo> getAssociationsForPackage(
@UserIdInt int userId, @NonNull String packageName) {
final List<AssociationInfo> associationsForUser = getAssociationsForUser(userId);
@@ -210,6 +264,9 @@
return Collections.unmodifiableList(associationsForPackage);
}
+ /**
+ * Get associations by mac address for the package.
+ */
public @Nullable AssociationInfo getAssociationsForPackageWithAddress(
@UserIdInt int userId, @NonNull String packageName, @NonNull String macAddress) {
final List<AssociationInfo> associations = getAssociationsByAddress(macAddress);
@@ -217,13 +274,20 @@
it -> it.belongsToPackage(userId, packageName));
}
+ /**
+ * Get association by id.
+ */
public @Nullable AssociationInfo getAssociationById(int id) {
synchronized (mLock) {
return mIdMap.get(id);
}
}
- public @NonNull List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
+ /**
+ * Get associations by mac address.
+ */
+ @NonNull
+ public List<AssociationInfo> getAssociationsByAddress(@NonNull String macAddress) {
final MacAddress address = MacAddress.fromString(macAddress);
synchronized (mLock) {
@@ -240,7 +304,8 @@
}
@GuardedBy("mLock")
- private @NonNull List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
+ @NonNull
+ private List<AssociationInfo> getAssociationsForUserLocked(@UserIdInt int userId) {
final List<AssociationInfo> cached = mCachedPerUser.get(userId);
if (cached != null) {
return cached;
@@ -262,12 +327,18 @@
mCachedPerUser.delete(userId);
}
+ /**
+ * Register a listener for association changes.
+ */
public void registerListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.add(listener);
}
}
+ /**
+ * Unregister a listener previously registered for association changes.
+ */
public void unregisterListener(@NonNull OnChangeListener listener) {
synchronized (mListeners) {
mListeners.remove(listener);
@@ -297,43 +368,30 @@
}
}
- void setAssociations(Collection<AssociationInfo> allAssociations) {
+ /**
+ * Set associations to cache. It will clear the existing cache.
+ */
+ public void setAssociationsToCache(Collection<AssociationInfo> associations) {
// Validity check first.
- allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+ associations.forEach(AssociationStore::checkNotRevoked);
- if (DEBUG) {
- Log.i(TAG, "setAssociations() n=" + allAssociations.size());
- final StringJoiner stringJoiner = new StringJoiner(", ");
- allAssociations.forEach(assoc -> stringJoiner.add(assoc.toShortString()));
- Log.v(TAG, " associations=" + stringJoiner);
- }
synchronized (mLock) {
- setAssociationsLocked(allAssociations);
- }
- }
+ mIdMap.clear();
+ mAddressMap.clear();
+ mCachedPerUser.clear();
- @GuardedBy("mLock")
- private void setAssociationsLocked(Collection<AssociationInfo> associations) {
- clearLocked();
+ for (AssociationInfo association : associations) {
+ final int id = association.getId();
+ mIdMap.put(id, association);
- for (AssociationInfo association : associations) {
- final int id = association.getId();
- mIdMap.put(id, association);
-
- final MacAddress address = association.getDeviceMacAddress();
- if (address != null) {
- mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ final MacAddress address = association.getDeviceMacAddress();
+ if (address != null) {
+ mAddressMap.computeIfAbsent(address, it -> new HashSet<>()).add(id);
+ }
}
}
}
- @GuardedBy("mLock")
- private void clearLocked() {
- mIdMap.clear();
- mAddressMap.clear();
- mCachedPerUser.clear();
- }
-
private static void checkNotRevoked(@NonNull AssociationInfo association) {
if (association.isRevoked()) {
throw new IllegalArgumentException(
diff --git a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
similarity index 84%
rename from services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
rename to services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
index aac628c..894c49a 100644
--- a/services/companion/java/com/android/server/companion/InactiveAssociationsRemovalService.java
+++ b/services/companion/java/com/android/server/companion/association/InactiveAssociationsRemovalService.java
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-package com.android.server.companion;
-
-import static com.android.server.companion.CompanionDeviceManagerService.TAG;
+package com.android.server.companion.association;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -29,13 +27,17 @@
import android.util.Slog;
import com.android.server.LocalServices;
+import com.android.server.companion.CompanionDeviceManagerServiceInternal;
/**
- * A Job Service responsible for clean up the Association.
+ * A Job Service responsible for clean up idle self-managed associations.
+ *
* The job will be executed only if the device is charging and in idle mode due to the application
- * will be killed if association/role are revoked.
+ * will be killed if association/role are revoked. See {@link AssociationRevokeProcessor}
*/
public class InactiveAssociationsRemovalService extends JobService {
+
+ private static final String TAG = "CDM_InactiveAssociationsRemovalService";
private static final String JOB_NAMESPACE = "companion";
private static final int JOB_ID = 1;
private static final long ONE_DAY_INTERVAL = DAYS.toMillis(1);
@@ -60,7 +62,10 @@
return false;
}
- static void schedule(Context context) {
+ /**
+ * Schedule this job.
+ */
+ public static void schedule(Context context) {
Slog.i(TAG, "Scheduling the Association Removal job");
final JobScheduler jobScheduler =
context.getSystemService(JobScheduler.class).forNamespace(JOB_NAMESPACE);
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 74236a4..a08e0da 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -52,8 +52,8 @@
import android.util.Slog;
import com.android.internal.R;
-import com.android.server.companion.AssociationStore;
import com.android.server.companion.CompanionDeviceManagerService;
+import com.android.server.companion.association.AssociationStore;
import com.android.server.companion.transport.CompanionTransportManager;
import com.android.server.companion.utils.PackageUtils;
import com.android.server.companion.utils.PermissionsUtils;
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
index 2899c05..99466a9 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
@@ -59,8 +59,8 @@
import android.util.Log;
import android.util.Slog;
-import com.android.server.companion.AssociationStore;
-import com.android.server.companion.AssociationStore.ChangeType;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.companion.association.AssociationStore.ChangeType;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
index 0287f62..4da3f9b 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
@@ -39,7 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.util.Arrays;
import java.util.Collections;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index 3da9693..37bbb93 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -44,7 +44,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
import java.util.HashSet;
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index 3861f99..6dd14ac 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -32,7 +32,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.server.companion.AssociationStore;
+import com.android.server.companion.association.AssociationStore;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/services/companion/java/com/android/server/companion/utils/AssociationUtils.java b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
new file mode 100644
index 0000000..e4d9641
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/utils/AssociationUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.utils;
+
+import android.annotation.UserIdInt;
+
+public final class AssociationUtils {
+
+ /** Range of Association IDs allocated for a user. */
+ private static final int ASSOCIATIONS_IDS_PER_USER_RANGE = 100000;
+
+ /**
+ * Get the left boundary of the association id range for the user.
+ */
+ public static int getFirstAssociationIdForUser(@UserIdInt int userId) {
+ // We want the IDs to start from 1, not 0.
+ return userId * ASSOCIATIONS_IDS_PER_USER_RANGE + 1;
+ }
+
+ /**
+ * Get the right boundary of the association id range for the user.
+ */
+ public static int getLastAssociationIdForUser(@UserIdInt int userId) {
+ return (userId + 1) * ASSOCIATIONS_IDS_PER_USER_RANGE;
+ }
+
+ private AssociationUtils() {}
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6d731b2..b1672ed 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -210,7 +210,7 @@
mActivityListener.onTopActivityChanged(displayId, topActivity,
UserHandle.USER_NULL);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -220,7 +220,7 @@
try {
mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
@@ -229,7 +229,7 @@
try {
mActivityListener.onDisplayEmpty(displayId);
} catch (RemoteException e) {
- Slog.w(TAG, "Unable to call mActivityListener", e);
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
};
@@ -1213,7 +1213,7 @@
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
- mContext.getUser());
+ UserHandle.SYSTEM);
}
private void onSecureWindowShown(int displayId, int uid) {
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index f82a6aa..748253f 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -106,7 +106,7 @@
private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
private static final int DEFAULT_MAX_FILES = 1000;
private static final int DEFAULT_MAX_FILES_LOWRAM = 300;
- private static final int DEFAULT_QUOTA_KB = 10 * 1024;
+ private static final int DEFAULT_QUOTA_KB = Build.IS_USERDEBUG ? 20 * 1024 : 10 * 1024;
private static final int DEFAULT_QUOTA_PERCENT = 10;
private static final int DEFAULT_RESERVE_PERCENT = 0;
private static final int QUOTA_RESCAN_MILLIS = 5000;
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 9189ea7..1a3ef73 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -264,8 +264,8 @@
final ArrayMap<String, ArraySet<String>> mAllowIgnoreLocationSettings = new ArrayMap<>();
// These are the packages that are allow-listed to be able to access camera when
- // the camera privacy state is for driver assistance apps only.
- final ArrayMap<String, Boolean> mAllowlistCameraPrivacy = new ArrayMap<>();
+ // the camera privacy state is enabled.
+ final ArraySet<String> mAllowlistCameraPrivacy = new ArraySet<>();
// These are the action strings of broadcasts which are whitelisted to
// be delivered anonymously even to apps which target O+.
@@ -348,6 +348,9 @@
// marked as stopped by the system
@NonNull private final Set<String> mInitialNonStoppedSystemPackages = new ArraySet<>();
+ // Which packages (key) are allowed to join particular SharedUid (value).
+ @NonNull private final Map<String, String> mPackageToSharedUidAllowList = new ArrayMap<>();
+
// A map of preloaded package names and the path to its app metadata file path.
private final ArrayMap<String, String> mAppMetadataFilePaths = new ArrayMap<>();
@@ -486,7 +489,7 @@
return mAllowedAssociations;
}
- public ArrayMap<String, Boolean> getCameraPrivacyAllowlist() {
+ public ArraySet<String> getCameraPrivacyAllowlist() {
return mAllowlistCameraPrivacy;
}
@@ -567,6 +570,11 @@
return mInitialNonStoppedSystemPackages;
}
+ @NonNull
+ public Map<String, String> getPackageToSharedUidAllowList() {
+ return mPackageToSharedUidAllowList;
+ }
+
public ArrayMap<String, String> getAppMetadataFilePaths() {
return mAppMetadataFilePaths;
}
@@ -1068,13 +1076,11 @@
case "camera-privacy-allowlisted-app" : {
if (allowOverrideAppRestrictions) {
String pkgname = parser.getAttributeValue(null, "package");
- boolean isMandatory = XmlUtils.readBooleanAttribute(
- parser, "mandatory", false);
if (pkgname == null) {
Slog.w(TAG, "<" + name + "> without package in "
+ permFile + " at " + parser.getPositionDescription());
} else {
- mAllowlistCameraPrivacy.put(pkgname, isMandatory);
+ mAllowlistCameraPrivacy.add(pkgname);
}
} else {
logNotAllowedInPartition(name, permFile, parser);
@@ -1563,6 +1569,19 @@
mInitialNonStoppedSystemPackages.add(pkgName);
}
} break;
+ case "allow-package-shareduid": {
+ String pkgName = parser.getAttributeValue(null, "package");
+ String sharedUid = parser.getAttributeValue(null, "shareduid");
+ if (TextUtils.isEmpty(pkgName)) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else if (TextUtils.isEmpty(sharedUid)) {
+ Slog.w(TAG, "<" + name + "> without shareduid in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mPackageToSharedUidAllowList.put(pkgName, sharedUid);
+ }
+ }
case "asl-file": {
String packageName = parser.getAttributeValue(null, "package");
String path = parser.getAttributeValue(null, "path");
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 5e9d1cb..25337a4 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -100,7 +100,7 @@
"file_patterns": ["VcnManagementService\\.java"]
},
{
- "name": "FrameworksNetTests",
+ "name": "FrameworksVpnTests",
"options": [
{
"exclude-annotation": "com.android.testutils.SkipPresubmit"
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index b8e09cc..258f53d 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -372,6 +372,15 @@
@Overridable
public static final long FGS_BOOT_COMPLETED_RESTRICTIONS = 296558535L;
+ /**
+ * Disables foreground service background starts in System Alert Window for all types
+ * unless it already has a System Overlay Window.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+ @Overridable
+ public static final long FGS_SAW_RESTRICTIONS = 319471980L;
+
final ActivityManagerService mAm;
// Maximum number of services that we allow to start in the background
@@ -8526,10 +8535,31 @@
}
}
+ // The flag being enabled isn't enough to deny background start: we need to also check
+ // if there is a system alert UI present.
if (ret == REASON_DENIED) {
- if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
- callingPackage)) {
- ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ // Flag check: are we disabling SAW FGS background starts?
+ final boolean shouldDisableSaw = Flags.fgsDisableSaw()
+ && CompatChanges.isChangeEnabled(FGS_BOOT_COMPLETED_RESTRICTIONS, callingUid);
+ if (shouldDisableSaw) {
+ final ProcessRecord processRecord = mAm
+ .getProcessRecordLocked(targetService.processName,
+ targetService.appInfo.uid);
+ if (processRecord != null) {
+ if (processRecord.mState.hasOverlayUi()) {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
+ }
+ } else {
+ Slog.e(TAG, "Could not find process record for SAW check");
+ }
+ } else {
+ if (mAm.mAtmInternal.hasSystemAlertWindowPermission(callingUid, callingPid,
+ callingPackage)) {
+ ret = REASON_SYSTEM_ALERT_WINDOW_PERMISSION;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 663ba8a..5e36709 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -141,7 +141,6 @@
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;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
@@ -726,29 +725,19 @@
// Whether we should use SCHED_FIFO for UI and RenderThreads.
final boolean mUseFifoUiScheduling;
- // Use an offload queue for long broadcasts, e.g. BOOT_COMPLETED.
- // For simplicity, since we statically declare the size of the array of BroadcastQueues,
- // we still create this new offload queue, but never ever put anything on it.
- final boolean mEnableOffloadQueue;
-
/**
* Flag indicating if we should use {@link BroadcastQueueModernImpl} instead
* of the default {@link BroadcastQueueImpl}.
*/
final boolean mEnableModernQueue;
- static final int BROADCAST_QUEUE_FG = 0;
- static final int BROADCAST_QUEUE_BG = 1;
- static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
- static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
-
@GuardedBy("this")
private final SparseArray<IUnsafeIntentStrictModeCallback>
mStrictModeCallbacks = new SparseArray<>();
// Convenient for easy iteration over the queues. Foreground is first
// so that dispatch of foreground broadcasts gets precedence.
- final BroadcastQueue[] mBroadcastQueues;
+ private BroadcastQueue mBroadcastQueue;
@GuardedBy("this")
BroadcastStats mLastBroadcastStats;
@@ -758,43 +747,6 @@
TraceErrorLogger mTraceErrorLogger;
- BroadcastQueue broadcastQueueForIntent(Intent intent) {
- return broadcastQueueForFlags(intent.getFlags(), intent);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags) {
- return broadcastQueueForFlags(flags, null);
- }
-
- BroadcastQueue broadcastQueueForFlags(int flags, Object cookie) {
- if (mEnableModernQueue) {
- return mBroadcastQueues[0];
- }
-
- if (isOnFgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on foreground offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD];
- }
-
- if (isOnBgOffloadQueue(flags)) {
- if (DEBUG_BROADCAST_BACKGROUND) {
- Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on background offload queue");
- }
- return mBroadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD];
- }
-
- final boolean isFg = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
- if (DEBUG_BROADCAST_BACKGROUND) Slog.i(TAG_BROADCAST,
- "Broadcast intent " + cookie + " on "
- + (isFg ? "foreground" : "background") + " queue");
- return (isFg) ? mBroadcastQueues[BROADCAST_QUEUE_FG]
- : mBroadcastQueues[BROADCAST_QUEUE_BG];
- }
-
private volatile int mDeviceOwnerUid = INVALID_UID;
/**
@@ -2556,9 +2508,8 @@
mInternal = new LocalService();
mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
- mEnableOffloadQueue = false;
mEnableModernQueue = false;
- mBroadcastQueues = injector.getBroadcastQueues(this);
+ mBroadcastQueue = injector.getBroadcastQueue(this);
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -2599,12 +2550,10 @@
? new OomAdjusterModernImpl(this, mProcessList, activeUids)
: new OomAdjuster(this, mProcessList, activeUids);
- mEnableOffloadQueue = SystemProperties.getBoolean(
- "persist.device_config.activity_manager_native_boot.offload_queue_enabled", true);
mEnableModernQueue = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS).MODERN_QUEUE_ENABLED;
- mBroadcastQueues = mInjector.getBroadcastQueues(this);
+ mBroadcastQueue = mInjector.getBroadcastQueue(this);
mServices = new ActiveServices(this);
mCpHelper = new ContentProviderHelper(this, true);
@@ -2671,6 +2620,14 @@
mComponentAliasResolver = new ComponentAliasResolver(this);
}
+ void setBroadcastQueueForTest(BroadcastQueue broadcastQueue) {
+ mBroadcastQueue = broadcastQueue;
+ }
+
+ BroadcastQueue getBroadcastQueue() {
+ return mBroadcastQueue;
+ }
+
public void setSystemServiceManager(SystemServiceManager mgr) {
mSystemServiceManager = mgr;
}
@@ -4280,19 +4237,14 @@
}
// Clean-up disabled broadcast receivers.
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- packageName, disabledClasses, userId);
- }
+ mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ packageName, disabledClasses, userId);
}
final boolean clearBroadcastQueueForUserLocked(int userId) {
- boolean didSomething = false;
- for (int i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
- null, null, userId);
- }
+ boolean didSomething = mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
+ null, null, userId);
return didSomething;
}
@@ -4445,10 +4397,8 @@
mUgmInternal.removeUriPermissionsForPackage(packageName, userId, false, false);
if (doit) {
- for (i = mBroadcastQueues.length - 1; i >= 0; i--) {
- didSomething |= mBroadcastQueues[i].cleanupDisabledPackageReceiversLocked(
+ didSomething |= mBroadcastQueue.cleanupDisabledPackageReceiversLocked(
packageName, null, userId);
- }
}
if (packageName == null || uninstalling || packageStateStopped) {
@@ -4515,9 +4465,7 @@
// Take care of any services that are waiting for the process.
mServices.processStartTimedOutLocked(app);
// Take care of any broadcasts waiting for the process.
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationTimeoutLocked(app);
- }
+ mBroadcastQueue.onApplicationTimeoutLocked(app);
if (!isKillTimeout) {
mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
app.killLocked("start timeout",
@@ -4779,36 +4727,47 @@
// being bound to an application.
thread.runIsolatedEntryPoint(
app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
- } else if (instr2 != null) {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- instr2.mIsSdkInSandbox,
- providerList,
- instr2.mClass,
- profilerInfo, instr2.mArguments,
- instr2.mWatcher,
- instr2.mUiAutomationConnection, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
- new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
- mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
} else {
- thread.bindApplication(processName, appInfo,
- app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
- /* isSdkInSandbox= */ false,
- providerList, null, profilerInfo, null, null, null, testMode,
- mBinderTransactionTrackingEnabled, enableTrackAllocation,
- isRestrictedBackupMode || !normalMode, app.isPersistent(),
+ boolean isSdkInSandbox = false;
+ ComponentName instrumentationName = null;
+ Bundle instrumentationArgs = null;
+ IInstrumentationWatcher instrumentationWatcher = null;
+ IUiAutomationConnection instrumentationUiConnection = null;
+ if (instr2 != null) {
+ isSdkInSandbox = instr2.mIsSdkInSandbox;
+ instrumentationName = instr2.mClass;
+ instrumentationArgs = instr2.mArguments;
+ instrumentationWatcher = instr2.mWatcher;
+ instrumentationUiConnection = instr2.mUiAutomationConnection;
+ }
+ thread.bindApplication(
+ processName,
+ appInfo,
+ app.sdkSandboxClientAppVolumeUuid,
+ app.sdkSandboxClientAppPackage,
+ isSdkInSandbox,
+ providerList,
+ instrumentationName,
+ profilerInfo,
+ instrumentationArgs,
+ instrumentationWatcher,
+ instrumentationUiConnection,
+ testMode,
+ mBinderTransactionTrackingEnabled,
+ enableTrackAllocation,
+ isRestrictedBackupMode || !normalMode,
+ app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
- app.getCompat(), getCommonServicesLocked(app.isolated),
+ app.getCompat(),
+ getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
- buildSerial, autofillOptions, contentCaptureOptions,
- app.getDisabledCompatChanges(), serializedSystemFontMap,
- app.getStartElapsedTime(), app.getStartUptime());
+ buildSerial,
+ autofillOptions,
+ contentCaptureOptions,
+ app.getDisabledCompatChanges(),
+ serializedSystemFontMap,
+ app.getStartElapsedTime(),
+ app.getStartUptime());
}
Message msg = mHandler.obtainMessage(BIND_APPLICATION_TIMEOUT_SOFT_MSG);
@@ -4948,9 +4907,7 @@
// Check if a next-broadcast receiver is in this process...
if (!badApp) {
try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
+ didSomething |= mBroadcastQueue.onApplicationAttachedLocked(app);
checkTime(startTime, "finishAttachApplicationInner: "
+ "after dispatching broadcasts");
} catch (BroadcastDeliveryFailedException e) {
@@ -9090,9 +9047,7 @@
}
private void startBroadcastObservers() {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.start(mContext.getContentResolver());
- }
+ mBroadcastQueue.start(mContext.getContentResolver());
}
private void updateForceBackgroundCheck(boolean enabled) {
@@ -9363,7 +9318,9 @@
sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
}
if (info.broadcastIntentAction != null) {
- sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+ sb.append("Broadcast-Intent-Action: ")
+ .append(info.broadcastIntentAction)
+ .append("\n");
}
if (info.durationMillis != -1) {
sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
@@ -9723,82 +9680,126 @@
// If process is null, we are being called from some internal code
// and may be about to die -- run this synchronously.
final boolean runSynchronously = process == null;
- Thread worker = new Thread("Error dump: " + dropboxTag) {
- @Override
- public void run() {
- if (report != null) {
- sb.append(report);
- }
-
- String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
- String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
- int lines = Build.IS_USER
- ? 0
- : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
- int dropboxMaxSize = Settings.Global.getInt(
- mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
-
- if (dataFile != null) {
- // Attach the stack traces file to the report so collectors can load them
- // by file if they have access.
- sb.append(DATA_FILE_PATH_HEADER)
- .append(dataFile.getAbsolutePath()).append('\n');
-
- int maxDataFileSize = dropboxMaxSize
- - sb.length()
- - lines * RESERVED_BYTES_PER_LOGCAT_LINE
- - DATA_FILE_PATH_FOOTER.length();
-
- if (maxDataFileSize > 0) {
- // Inline dataFile contents if there is room.
- try {
- sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
- "\n\n[[TRUNCATED]]\n"));
- } catch (IOException e) {
- Slog.e(TAG, "Error reading " + dataFile, e);
+ Thread worker =
+ new Thread("Error dump: " + dropboxTag) {
+ @Override
+ public void run() {
+ if (report != null) {
+ sb.append(report);
}
+
+ String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+ String maxBytesSetting =
+ Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag;
+ int lines =
+ Build.IS_USER
+ ? 0
+ : Settings.Global.getInt(
+ mContext.getContentResolver(), logcatSetting, 0);
+ int dropboxMaxSize =
+ Settings.Global.getInt(
+ mContext.getContentResolver(),
+ maxBytesSetting,
+ DROPBOX_DEFAULT_MAX_SIZE);
+
+ if (dataFile != null) {
+ // Attach the stack traces file to the report so collectors can load
+ // them
+ // by file if they have access.
+ sb.append(DATA_FILE_PATH_HEADER)
+ .append(dataFile.getAbsolutePath())
+ .append('\n');
+
+ int maxDataFileSize =
+ dropboxMaxSize
+ - sb.length()
+ - lines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - DATA_FILE_PATH_FOOTER.length();
+
+ if (maxDataFileSize > 0) {
+ // Inline dataFile contents if there is room.
+ try {
+ sb.append(
+ FileUtils.readTextFile(
+ dataFile,
+ maxDataFileSize,
+ "\n\n[[TRUNCATED]]\n"));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading " + dataFile, e);
+ }
+ }
+
+ // Always append the footer, even there wasn't enough space to inline
+ // the
+ // dataFile contents.
+ sb.append(DATA_FILE_PATH_FOOTER);
+ }
+
+ if (crashInfo != null && crashInfo.stackTrace != null) {
+ sb.append(crashInfo.stackTrace);
+ }
+
+ if (lines > 0 && !runSynchronously) {
+ sb.append("\n");
+
+ InputStreamReader input = null;
+ try {
+ java.lang.Process logcat =
+ new ProcessBuilder(
+ // Time out after 10s of inactivity, but
+ // kill logcat with SEGV
+ // so we can investigate why it didn't
+ // finish.
+ "/system/bin/timeout",
+ "-i",
+ "-s",
+ "SEGV",
+ "10s",
+ // Merge several logcat streams, and take
+ // the last N lines.
+ "/system/bin/logcat",
+ "-v",
+ "threadtime",
+ "-b",
+ "events",
+ "-b",
+ "system",
+ "-b",
+ "main",
+ "-b",
+ "crash",
+ "-t",
+ String.valueOf(lines))
+ .redirectErrorStream(true)
+ .start();
+
+ try {
+ logcat.getOutputStream().close();
+ } catch (IOException e) {
+ }
+ try {
+ logcat.getErrorStream().close();
+ } catch (IOException e) {
+ }
+ input = new InputStreamReader(logcat.getInputStream());
+
+ int num;
+ char[] buf = new char[8192];
+ while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
+ } catch (IOException e) {
+ Slog.e(TAG, "Error running logcat", e);
+ } finally {
+ if (input != null)
+ try {
+ input.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ dbox.addText(dropboxTag, sb.toString());
}
-
- // Always append the footer, even there wasn't enough space to inline the
- // dataFile contents.
- sb.append(DATA_FILE_PATH_FOOTER);
- }
-
- if (crashInfo != null && crashInfo.stackTrace != null) {
- sb.append(crashInfo.stackTrace);
- }
-
- if (lines > 0 && !runSynchronously) {
- sb.append("\n");
-
- InputStreamReader input = null;
- try {
- java.lang.Process logcat = new ProcessBuilder(
- // Time out after 10s of inactivity, but kill logcat with SEGV
- // so we can investigate why it didn't finish.
- "/system/bin/timeout", "-i", "-s", "SEGV", "10s",
- // Merge several logcat streams, and take the last N lines.
- "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
- "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
- .redirectErrorStream(true).start();
-
- try { logcat.getOutputStream().close(); } catch (IOException e) {}
- try { logcat.getErrorStream().close(); } catch (IOException e) {}
- input = new InputStreamReader(logcat.getInputStream());
-
- int num;
- char[] buf = new char[8192];
- while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
- } catch (IOException e) {
- Slog.e(TAG, "Error running logcat", e);
- } finally {
- if (input != null) try { input.close(); } catch (IOException e) {}
- }
- }
-
- dbox.addText(dropboxTag, sb.toString());
- }
- };
+ };
if (runSynchronously) {
final int oldMask = StrictMode.allowThreadDiskWritesMask();
@@ -10166,43 +10167,48 @@
mOomAdjuster.dumpCacheOomRankerSettings(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAllowedAssociationsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
-
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mPendingIntentController.dumpPendingIntents(pw, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
if (dumpAll || dumpPackage != null) {
dumpBroadcastStatsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
}
mCpHelper.dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpPermissions(fd, pw, args, opti, dumpAll, dumpPackage);
pw.println();
sdumper = mServices.newServiceDumperLocked(fd, pw, args, opti, dumpAll, dumpPackage);
if (!dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpLocked();
}
@@ -10217,7 +10223,8 @@
// method with the lock held.
if (dumpClient) {
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
sdumper.dumpWithClient();
}
@@ -10230,33 +10237,38 @@
// proxies in the first place.
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpBinderProxies(pw, BINDER_PROXY_HIGH_WATERMARK /* minToDump */);
}
synchronized(this) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10266,7 +10278,8 @@
if (!dumpNormalPriority) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
dumpPackage, displayIdFilter);
@@ -10274,45 +10287,53 @@
if (mAssociations.size() > 0) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpAssociationsLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
}
if (dumpPackage == null) {
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mOomAdjProfiler.dump(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpLmkLocked(pw);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
synchronized (mProcLock) {
mProcessList.dumpProcessesLSP(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
}
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
dumpUsers(pw);
pw.println();
if (dumpAll) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
}
mComponentAliasResolver.dump(pw);
}
@@ -10322,7 +10343,8 @@
* Dump the app restriction controller, it's required not to hold the global lock here.
*/
private void dumpAppRestrictionController(PrintWriter pw) {
- pw.println("-------------------------------------------------------------------------------");
+ pw.println(
+ "-------------------------------------------------------------------------------");
mAppRestrictionController.dump(pw, "");
}
@@ -11431,9 +11453,7 @@
}
}
mReceiverResolver.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.RECEIVER_RESOLVER);
- for (BroadcastQueue q : mBroadcastQueues) {
- q.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
- }
+ mBroadcastQueue.dumpDebug(proto, ActivityManagerServiceDumpBroadcastsProto.BROADCAST_QUEUE);
synchronized (mStickyBroadcasts) {
for (int user = 0; user < mStickyBroadcasts.size(); user++) {
long token = proto.start(
@@ -11462,7 +11482,9 @@
void dumpAllowedAssociationsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage) {
- pw.println("ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity allowed-associations)");
+ pw.println(
+ "ACTIVITY MANAGER ALLOWED ASSOCIATION STATE (dumpsys activity"
+ + " allowed-associations)");
boolean printed = false;
if (mAllowedAssociations != null) {
for (int i = 0; i < mAllowedAssociations.size(); i++) {
@@ -11581,11 +11603,9 @@
}
if (!onlyReceivers) {
- for (BroadcastQueue q : mBroadcastQueues) {
- needSep = q.dumpLocked(fd, pw, args, opti,
- dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
- printedAnything |= needSep;
- }
+ needSep = mBroadcastQueue.dumpLocked(fd, pw, args, opti,
+ dumpConstants, dumpHistory, dumpAll, dumpPackage, needSep);
+ printedAnything |= needSep;
}
needSep = true;
@@ -11641,9 +11661,8 @@
if (!onlyHistory && !onlyReceivers && dumpAll) {
pw.println();
- for (BroadcastQueue queue : mBroadcastQueues) {
- pw.println(" Queue " + queue.toString() + ": " + queue.describeStateLocked());
- }
+ pw.println(" Queue " + mBroadcastQueue.toString() + ": "
+ + mBroadcastQueue.describeStateLocked());
pw.println(" mHandler:");
mHandler.dump(new PrintWriterPrinter(pw), " ");
needSep = true;
@@ -12816,7 +12835,8 @@
if (!opts.isCompact) {
pw.print(" Used RAM: "); pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss
+ kernelUsed)); pw.print(" (");
- pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss)); pw.print(" used pss + ");
+ pw.print(stringifyKBSize(ss[INDEX_TOTAL_PSS] - cachedPss));
+ pw.print(" used pss + ");
pw.print(stringifyKBSize(kernelUsed)); pw.print(" kernel)\n");
pw.print(" Lost RAM: "); pw.println(stringifyKBSize(lostRAM));
} else {
@@ -13490,9 +13510,7 @@
mOomAdjuster.mCachedAppOptimizer.onCleanupApplicationRecordLocked(app);
}
mAppProfiler.onCleanupApplicationRecordLocked(app);
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.onApplicationCleanupLocked(app);
- }
+ mBroadcastQueue.onApplicationCleanupLocked(app);
clearProcessForegroundLocked(app);
mServices.killServicesLocked(app, allowRestart);
mPhantomProcessList.onAppDied(pid);
@@ -13686,8 +13704,15 @@
}
validateServiceInstanceName(instanceName);
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE,
- "*** startService: " + service + " type=" + resolvedType + " fg=" + requireForeground);
+ if (DEBUG_SERVICE)
+ Slog.v(
+ TAG_SERVICE,
+ "*** startService: "
+ + service
+ + " type="
+ + resolvedType
+ + " fg="
+ + requireForeground);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
@@ -14599,7 +14624,7 @@
originalStickyCallingUid))) {
sticky = broadcast.intent;
}
- BroadcastQueue queue = broadcastQueueForIntent(broadcast.intent);
+ BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, broadcast.intent, null,
null, null, -1, -1, false, null, null, null, null, OP_NONE,
BroadcastOptions.makeWithDeferUntilActive(broadcast.deferUntilActive),
@@ -15448,9 +15473,14 @@
if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
callingPid, callingUid)
!= PackageManager.PERMISSION_GRANTED) {
- String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
- + callingPid + ", uid=" + callingUid
- + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+ String msg =
+ "Permission Denial: broadcastIntent() requesting a sticky broadcast from"
+ + " pid="
+ + callingPid
+ + ", uid="
+ + callingUid
+ + " requires "
+ + android.Manifest.permission.BROADCAST_STICKY;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}
@@ -15593,7 +15623,7 @@
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
- final BroadcastQueue queue = broadcastQueueForIntent(intent);
+ final BroadcastQueue queue = mBroadcastQueue;
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
@@ -15686,7 +15716,7 @@
if ((receivers != null && receivers.size() > 0)
|| resultTo != null) {
- BroadcastQueue queue = broadcastQueueForIntent(intent);
+ BroadcastQueue queue = mBroadcastQueue;
filterNonExportedComponents(intent, callingUid, callingPid, receivers,
mPlatformCompat, callerPackage, resolvedType);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
@@ -15974,9 +16004,7 @@
}
void backgroundServicesFinishedLocked(int userId) {
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.backgroundServicesFinishedLocked(userId);
- }
+ mBroadcastQueue.backgroundServicesFinishedLocked(userId);
}
public void finishReceiver(IBinder caller, int resultCode, String resultData,
@@ -15997,8 +16025,7 @@
return;
}
- final BroadcastQueue queue = broadcastQueueForFlags(flags);
- queue.finishReceiverLocked(callerApp, resultCode,
+ mBroadcastQueue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -16589,10 +16616,7 @@
// =========================================================
boolean isReceivingBroadcastLocked(ProcessRecord app, int[] outSchedGroup) {
- int res = ProcessList.SCHED_GROUP_UNDEFINED;
- for (BroadcastQueue queue : mBroadcastQueues) {
- res = Math.max(res, queue.getPreferredSchedulingGroupLocked(app));
- }
+ final int res = mBroadcastQueue.getPreferredSchedulingGroupLocked(app);
outSchedGroup[0] = res;
return res != ProcessList.SCHED_GROUP_UNDEFINED;
}
@@ -16716,10 +16740,8 @@
*/
@GuardedBy("this")
final boolean canGcNowLocked() {
- for (BroadcastQueue q : mBroadcastQueues) {
- if (!q.isIdleLocked()) {
- return false;
- }
+ if (!mBroadcastQueue.isIdleLocked()) {
+ return false;
}
return mAtmInternal.canGcNow();
}
@@ -17929,9 +17951,7 @@
}
void onProcessFreezableChangedLocked(ProcessRecord app) {
- if (mEnableModernQueue) {
- mBroadcastQueues[0].onProcessFreezableChangedLocked(app);
- }
+ mBroadcastQueue.onProcessFreezableChangedLocked(app);
}
@VisibleForTesting
@@ -19622,9 +19642,7 @@
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForIdle(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForIdle(pw);
- }
+ mBroadcastQueue.waitForIdle(pw);
pw.println("All broadcast queues are idle!");
pw.flush();
}
@@ -19640,9 +19658,7 @@
if (flushBroadcastLoopers) {
BroadcastLoopers.waitForBarrier(pw);
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForBarrier(pw);
- }
+ mBroadcastQueue.waitForBarrier(pw);
if (flushApplicationThreads) {
waitForApplicationBarrier(pw);
}
@@ -19718,9 +19734,7 @@
void waitForBroadcastDispatch(@NonNull PrintWriter pw, @NonNull Intent intent) {
enforceCallingPermission(permission.DUMP, "waitForBroadcastDispatch");
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.waitForDispatched(intent, pw);
- }
+ mBroadcastQueue.waitForDispatched(intent, pw);
}
void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
@@ -19765,9 +19779,7 @@
return;
}
- for (BroadcastQueue queue : mBroadcastQueues) {
- queue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
- }
+ mBroadcastQueue.forceDelayBroadcastDelivery(targetPackage, delayedDurationMs);
}
@Override
@@ -20321,7 +20333,7 @@
return mNmi != null;
}
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
// Broadcast policy parameters
final BroadcastConstants foreConstants = new BroadcastConstants(
Settings.Global.BROADCAST_FG_CONSTANTS);
@@ -20337,26 +20349,8 @@
// by default, no "slow" policy in this queue
offloadConstants.SLOW_TIME = Integer.MAX_VALUE;
- final BroadcastQueue[] broadcastQueues;
- final Handler handler = service.mHandler;
- if (service.mEnableModernQueue) {
- broadcastQueues = new BroadcastQueue[1];
- broadcastQueues[0] = new BroadcastQueueModernImpl(service, handler,
+ return new BroadcastQueueModernImpl(service, service.mHandler,
foreConstants, backConstants);
- } else {
- broadcastQueues = new BroadcastQueue[4];
- broadcastQueues[BROADCAST_QUEUE_FG] = new BroadcastQueueImpl(service, handler,
- "foreground", foreConstants, false, ProcessList.SCHED_GROUP_DEFAULT);
- broadcastQueues[BROADCAST_QUEUE_BG] = new BroadcastQueueImpl(service, handler,
- "background", backConstants, true, ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_BG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_bg", offloadConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- broadcastQueues[BROADCAST_QUEUE_FG_OFFLOAD] = new BroadcastQueueImpl(service,
- handler, "offload_fg", foreConstants, true,
- ProcessList.SCHED_GROUP_BACKGROUND);
- }
- return broadcastQueues;
}
/** @see Binder#getCallingUid */
@@ -20678,14 +20672,6 @@
}
}
- private boolean isOnFgOffloadQueue(int flags) {
- return ((flags & Intent.FLAG_RECEIVER_OFFLOAD_FOREGROUND) != 0);
- }
-
- private boolean isOnBgOffloadQueue(int flags) {
- return (mEnableOffloadQueue && ((flags & Intent.FLAG_RECEIVER_OFFLOAD) != 0));
- }
-
@Override
public ParcelFileDescriptor getLifeMonitor() {
if (!isCallerShell()) {
diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java
deleted file mode 100644
index 8aa3921..0000000
--- a/services/core/java/com/android/server/am/BroadcastDispatcher.java
+++ /dev/null
@@ -1,1266 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.am;
-
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UptimeMillisLong;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Handler;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.LocalServices;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-
-/**
- * Manages ordered broadcast delivery, applying policy to mitigate the effects of
- * slow receivers.
- */
-public class BroadcastDispatcher {
- private static final String TAG = "BroadcastDispatcher";
-
- // Deferred broadcasts to one app; times are all uptime time base like
- // other broadcast-related timekeeping
- static class Deferrals {
- final int uid;
- long deferredAt; // when we started deferring
- long deferredBy; // how long did we defer by last time?
- long deferUntil; // when does the next element become deliverable?
- int alarmCount;
-
- final ArrayList<BroadcastRecord> broadcasts;
-
- Deferrals(int uid, long now, long backoff, int count) {
- this.uid = uid;
- this.deferredAt = now;
- this.deferredBy = backoff;
- this.deferUntil = now + backoff;
- this.alarmCount = count;
- broadcasts = new ArrayList<>();
- }
-
- void add(BroadcastRecord br) {
- broadcasts.add(br);
- }
-
- int size() {
- return broadcasts.size();
- }
-
- boolean isEmpty() {
- return broadcasts.isEmpty();
- }
-
- @NeverCompile
- void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (BroadcastRecord br : broadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- void dumpLocked(Dumper d) {
- for (BroadcastRecord br : broadcasts) {
- d.dump(br);
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("Deferrals{uid=");
- sb.append(uid);
- sb.append(", deferUntil=");
- sb.append(deferUntil);
- sb.append(", #broadcasts=");
- sb.append(broadcasts.size());
- sb.append("}");
- return sb.toString();
- }
- }
-
- // Carrying dump formatting state across multiple concatenated datasets
- class Dumper {
- final PrintWriter mPw;
- final String mQueueName;
- final String mDumpPackage;
- final SimpleDateFormat mSdf;
- boolean mPrinted;
- boolean mNeedSep;
- String mHeading;
- String mLabel;
- int mOrdinal;
-
- Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) {
- mPw = pw;
- mQueueName = queueName;
- mDumpPackage = dumpPackage;
- mSdf = sdf;
-
- mPrinted = false;
- mNeedSep = true;
- }
-
- void setHeading(String heading) {
- mHeading = heading;
- mPrinted = false;
- }
-
- void setLabel(String label) {
- //" Active Ordered Broadcast " + mQueueName + " #" + i + ":"
- mLabel = " " + label + " " + mQueueName + " #";
- mOrdinal = 0;
- }
-
- boolean didPrint() {
- return mPrinted;
- }
-
- @NeverCompile
- void dump(BroadcastRecord br) {
- if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) {
- if (!mPrinted) {
- if (mNeedSep) {
- mPw.println();
- }
- mPrinted = true;
- mNeedSep = true;
- mPw.println(" " + mHeading + " [" + mQueueName + "]:");
- }
- mPw.println(mLabel + mOrdinal + ":");
- mOrdinal++;
-
- br.dump(mPw, " ", mSdf);
- }
- }
- }
-
- private final Object mLock;
- private final BroadcastQueueImpl mQueue;
- private final BroadcastConstants mConstants;
- private final Handler mHandler;
- private AlarmManagerInternal mAlarm;
-
- // Current alarm targets; mapping uid -> in-flight alarm count
- final SparseIntArray mAlarmUids = new SparseIntArray();
- final AlarmManagerInternal.InFlightListener mAlarmListener =
- new AlarmManagerInternal.InFlightListener() {
- @Override
- public void broadcastAlarmPending(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) + 1;
- mAlarmUids.put(recipientUid, newCount);
- // any deferred broadcasts to this app now get fast-tracked
- final int numEntries = mDeferredBroadcasts.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mDeferredBroadcasts.get(i).uid) {
- Deferrals d = mDeferredBroadcasts.remove(i);
- mAlarmDeferrals.add(d);
- break;
- }
- }
- }
- }
-
- @Override
- public void broadcastAlarmComplete(final int recipientUid) {
- synchronized (mLock) {
- final int newCount = mAlarmUids.get(recipientUid, 0) - 1;
- if (newCount >= 0) {
- mAlarmUids.put(recipientUid, newCount);
- } else {
- Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid);
- mAlarmUids.put(recipientUid, 0);
- }
-
- // No longer an alarm target, so resume ordinary deferral policy
- if (newCount <= 0) {
- final int numEntries = mAlarmDeferrals.size();
- for (int i = 0; i < numEntries; i++) {
- if (recipientUid == mAlarmDeferrals.get(i).uid) {
- Deferrals d = mAlarmDeferrals.remove(i);
- insertLocked(mDeferredBroadcasts, d);
- break;
- }
- }
- }
- }
- }
- };
-
- // Queue recheck operation used to tickle broadcast delivery when appropriate
- final Runnable mScheduleRunnable = new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.v(TAG, "Deferral recheck of pending broadcasts");
- }
- mQueue.scheduleBroadcastsLocked();
- mRecheckScheduled = false;
- }
- }
- };
- private boolean mRecheckScheduled = false;
-
- // Usual issuance-order outbound queue
- private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
- // General deferrals not holding up alarms
- private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>();
- // Deferrals that *are* holding up alarms; ordered by alarm dispatch time
- private final ArrayList<Deferrals> mAlarmDeferrals = new ArrayList<>();
- // Under the "deliver alarm broadcasts immediately" policy, the queue of
- // upcoming alarm broadcasts. These are always delivered first - if the
- // policy is changed on the fly from immediate-alarm-delivery to the previous
- // in-order-queueing behavior, pending immediate alarm deliveries will drain
- // and then the behavior settle into the pre-U semantics.
- private final ArrayList<BroadcastRecord> mAlarmQueue = new ArrayList<>();
-
- // Next outbound broadcast, established by getNextBroadcastLocked()
- private BroadcastRecord mCurrentBroadcast;
-
- // Map userId to its deferred boot completed broadcasts.
- private SparseArray<DeferredBootCompletedBroadcastPerUser> mUser2Deferred = new SparseArray<>();
-
- /**
- * Deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts that is sent to a user.
- */
- static class DeferredBootCompletedBroadcastPerUser {
- private int mUserId;
- // UID that has process started at least once, ready to execute LOCKED_BOOT_COMPLETED
- // receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForLockedBootCompletedBroadcast = new SparseBooleanArray();
- // UID that has process started at least once, ready to execute BOOT_COMPLETED receivers.
- @VisibleForTesting
- SparseBooleanArray mUidReadyForBootCompletedBroadcast = new SparseBooleanArray();
- // Map UID to deferred LOCKED_BOOT_COMPLETED broadcasts.
- // LOCKED_BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has
- // any process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredLockedBootCompletedBroadcasts = new SparseArray<>();
- // is the LOCKED_BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mLockedBootCompletedBroadcastReceived;
- // Map UID to deferred BOOT_COMPLETED broadcasts.
- // BOOT_COMPLETED broadcast receivers are deferred until the first time the uid has any
- // process started.
- @VisibleForTesting
- SparseArray<BroadcastRecord> mDeferredBootCompletedBroadcasts = new SparseArray<>();
- // is the BOOT_COMPLETED broadcast received by the user.
- @VisibleForTesting
- boolean mBootCompletedBroadcastReceived;
-
- DeferredBootCompletedBroadcastPerUser(int userId) {
- this.mUserId = userId;
- }
-
- public void updateUidReady(int uid) {
- if (!mLockedBootCompletedBroadcastReceived
- || mDeferredLockedBootCompletedBroadcasts.size() != 0) {
- mUidReadyForLockedBootCompletedBroadcast.put(uid, true);
- }
- if (!mBootCompletedBroadcastReceived
- || mDeferredBootCompletedBroadcasts.size() != 0) {
- mUidReadyForBootCompletedBroadcast.put(uid, true);
- }
- }
-
- public void enqueueBootCompletedBroadcasts(String action,
- SparseArray<BroadcastRecord> deferred) {
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast);
- mLockedBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredLockedBootCompletedBroadcasts);
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- enqueueBootCompletedBroadcasts(deferred, mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast);
- mBootCompletedBroadcastReceived = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- dumpBootCompletedBroadcastRecord(mDeferredBootCompletedBroadcasts);
- }
- }
- }
-
- /**
- * Merge UID to BroadcastRecord map into {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- * @param from the UID to BroadcastRecord map.
- * @param into The UID to list of BroadcastRecord map.
- */
- private void enqueueBootCompletedBroadcasts(SparseArray<BroadcastRecord> from,
- SparseArray<BroadcastRecord> into, SparseBooleanArray uidReadyForReceiver) {
- // remove unwanted uids from uidReadyForReceiver.
- for (int i = uidReadyForReceiver.size() - 1; i >= 0; i--) {
- if (from.indexOfKey(uidReadyForReceiver.keyAt(i)) < 0) {
- uidReadyForReceiver.removeAt(i);
- }
- }
- for (int i = 0, size = from.size(); i < size; i++) {
- final int uid = from.keyAt(i);
- into.put(uid, from.valueAt(i));
- if (uidReadyForReceiver.indexOfKey(uid) < 0) {
- // uid is wanted but not ready.
- uidReadyForReceiver.put(uid, false);
- }
- }
- }
-
- public @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- boolean isAllUidReady) {
- BroadcastRecord next = dequeueDeferredBootCompletedBroadcast(
- mDeferredLockedBootCompletedBroadcasts,
- mUidReadyForLockedBootCompletedBroadcast, isAllUidReady);
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast(mDeferredBootCompletedBroadcasts,
- mUidReadyForBootCompletedBroadcast, isAllUidReady);
- }
- return next;
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast(
- SparseArray<BroadcastRecord> uid2br, SparseBooleanArray uidReadyForReceiver,
- boolean isAllUidReady) {
- for (int i = 0, size = uid2br.size(); i < size; i++) {
- final int uid = uid2br.keyAt(i);
- if (isAllUidReady || uidReadyForReceiver.get(uid)) {
- final BroadcastRecord br = uid2br.valueAt(i);
- if (DEBUG_BROADCAST_DEFERRAL) {
- final Object receiver = br.receivers.get(0);
- if (receiver instanceof BroadcastFilter) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " BroadcastFilter:" + (BroadcastFilter) receiver
- + " broadcast:" + br.intent.getAction());
- }
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- String packageName = info.activityInfo.applicationInfo.packageName;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "getDeferredBootCompletedBroadcast uid:" + uid
- + " packageName:" + packageName
- + " broadcast:" + br.intent.getAction());
- }
- }
- }
- // remove the BroadcastRecord.
- uid2br.removeAt(i);
- if (uid2br.size() == 0) {
- // All deferred receivers are executed, do not need uidReadyForReceiver
- // any more.
- uidReadyForReceiver.clear();
- }
- return br;
- }
- }
- return null;
- }
-
- private @Nullable SparseArray<BroadcastRecord> getDeferredList(String action) {
- SparseArray<BroadcastRecord> brs = null;
- if (action.equals(Intent.ACTION_LOCKED_BOOT_COMPLETED)) {
- brs = mDeferredLockedBootCompletedBroadcasts;
- } else if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- brs = mDeferredBootCompletedBroadcasts;
- }
- return brs;
- }
-
- /**
- * Return the total number of UIDs in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- return brs != null ? brs.size() : 0;
- }
-
- /**
- * Return the total number of receivers in all BroadcastRecord in
- * {@link #mDeferredBootCompletedBroadcasts} or
- * {@link #mDeferredLockedBootCompletedBroadcasts}
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return 0;
- }
- int size = 0;
- for (int i = 0, s = brs.size(); i < s; i++) {
- size += brs.valueAt(i).receivers.size();
- }
- return size;
- }
-
- @NeverCompile
- public void dump(Dumper dumper, String action) {
- SparseArray<BroadcastRecord> brs = getDeferredList(action);
- if (brs == null) {
- return;
- }
- for (int i = 0, size = brs.size(); i < size; i++) {
- dumper.dump(brs.valueAt(i));
- }
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- for (int i = 0, size = mDeferredLockedBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredLockedBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- for (int i = 0, size = mDeferredBootCompletedBroadcasts.size(); i < size; i++) {
- mDeferredBootCompletedBroadcasts.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- @NeverCompile
- private void dumpBootCompletedBroadcastRecord(SparseArray<BroadcastRecord> brs) {
- for (int i = 0, size = brs.size(); i < size; i++) {
- final Object receiver = brs.valueAt(i).receivers.get(0);
- String packageName = null;
- if (receiver instanceof BroadcastFilter) {
- BroadcastFilter recv = (BroadcastFilter) receiver;
- packageName = recv.receiverList.app.processName;
- } else /* if (receiver instanceof ResolveInfo) */ {
- ResolveInfo info = (ResolveInfo) receiver;
- packageName = info.activityInfo.applicationInfo.packageName;
- }
- Slog.i(TAG, "uid:" + brs.keyAt(i)
- + " packageName:" + packageName
- + " receivers:" + brs.valueAt(i).receivers.size());
- }
- }
- }
-
- private DeferredBootCompletedBroadcastPerUser getDeferredPerUser(int userId) {
- if (mUser2Deferred.contains(userId)) {
- return mUser2Deferred.get(userId);
- } else {
- final DeferredBootCompletedBroadcastPerUser temp =
- new DeferredBootCompletedBroadcastPerUser(userId);
- mUser2Deferred.put(userId, temp);
- return temp;
- }
- }
-
- /**
- * ActivityManagerService.attachApplication() call this method to notify that the UID is ready
- * to accept deferred LOCKED_BOOT_COMPLETED and BOOT_COMPLETED broadcasts.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- getDeferredPerUser(UserHandle.getUserId(uid)).updateUidReady(uid);
- }
-
- private @Nullable BroadcastRecord dequeueDeferredBootCompletedBroadcast() {
- final boolean isAllUidReady = (mQueue.mService.mConstants.mDeferBootCompletedBroadcast
- == DEFER_BOOT_COMPLETED_BROADCAST_NONE);
- BroadcastRecord next = null;
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- next = mUser2Deferred.valueAt(i).dequeueDeferredBootCompletedBroadcast(isAllUidReady);
- if (next != null) {
- break;
- }
- }
- return next;
- }
-
- /**
- * Constructed & sharing a lock with its associated BroadcastQueue instance
- */
- public BroadcastDispatcher(BroadcastQueueImpl queue, BroadcastConstants constants,
- Handler handler, Object lock) {
- mQueue = queue;
- mConstants = constants;
- mHandler = handler;
- mLock = lock;
- }
-
- /**
- * Spin up the integration with the alarm manager service; done lazily to manage
- * service availability ordering during boot.
- */
- public void start() {
- // Set up broadcast alarm tracking
- mAlarm = LocalServices.getService(AlarmManagerInternal.class);
- mAlarm.registerInFlightListener(mAlarmListener);
- }
-
- /**
- * Standard contents-are-empty check
- */
- public boolean isEmpty() {
- synchronized (mLock) {
- return isIdle()
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED) == 0
- && getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED) == 0;
- }
- }
-
- /**
- * Have less check than {@link #isEmpty()}.
- * The dispatcher is considered as idle even with deferred LOCKED_BOOT_COMPLETED/BOOT_COMPLETED
- * broadcasts because those can be deferred until the first time the uid's process is started.
- * @return
- */
- public boolean isIdle() {
- synchronized (mLock) {
- return mCurrentBroadcast == null
- && mOrderedBroadcasts.isEmpty()
- && mAlarmQueue.isEmpty()
- && isDeferralsListEmpty(mDeferredBroadcasts)
- && isDeferralsListEmpty(mAlarmDeferrals);
- }
- }
-
- private static boolean isDeferralsBeyondBarrier(@NonNull ArrayList<Deferrals> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (!isBeyondBarrier(list.get(i).broadcasts, barrierTime)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isBeyondBarrier(@NonNull ArrayList<BroadcastRecord> list,
- @UptimeMillisLong long barrierTime) {
- for (int i = 0; i < list.size(); i++) {
- if (list.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isBeyondBarrier(@UptimeMillisLong long barrierTime) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && mCurrentBroadcast.enqueueTime <= barrierTime) {
- return false;
- }
- return isBeyondBarrier(mOrderedBroadcasts, barrierTime)
- && isBeyondBarrier(mAlarmQueue, barrierTime)
- && isDeferralsBeyondBarrier(mDeferredBroadcasts, barrierTime)
- && isDeferralsBeyondBarrier(mAlarmDeferrals, barrierTime);
- }
- }
-
- private static boolean isDispatchedInDeferrals(@NonNull ArrayList<Deferrals> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (!isDispatched(list.get(i).broadcasts, intent)) {
- return false;
- }
- }
- return true;
- }
-
- private static boolean isDispatched(@NonNull ArrayList<BroadcastRecord> list,
- @NonNull Intent intent) {
- for (int i = 0; i < list.size(); i++) {
- if (intent.filterEquals(list.get(i).intent)) {
- return false;
- }
- }
- return true;
- }
-
- public boolean isDispatched(@NonNull Intent intent) {
- synchronized (mLock) {
- if ((mCurrentBroadcast != null) && intent.filterEquals(mCurrentBroadcast.intent)) {
- return false;
- }
- return isDispatched(mOrderedBroadcasts, intent)
- && isDispatched(mAlarmQueue, intent)
- && isDispatchedInDeferrals(mDeferredBroadcasts, intent)
- && isDispatchedInDeferrals(mAlarmDeferrals, intent);
- }
- }
-
- private static int pendingInDeferralsList(ArrayList<Deferrals> list) {
- int pending = 0;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- pending += list.get(i).size();
- }
- return pending;
- }
-
- private static boolean isDeferralsListEmpty(ArrayList<Deferrals> list) {
- return pendingInDeferralsList(list) == 0;
- }
-
- /**
- * Strictly for logging, describe the currently pending contents in a human-
- * readable way
- */
- public String describeStateLocked() {
- final StringBuilder sb = new StringBuilder(128);
- if (mCurrentBroadcast != null) {
- sb.append("1 in flight, ");
- }
- sb.append(mOrderedBroadcasts.size());
- sb.append(" ordered");
- int n = mAlarmQueue.size();
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" alarms");
- }
- n = pendingInDeferralsList(mAlarmDeferrals);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferrals in alarm recipients");
- }
- n = pendingInDeferralsList(mDeferredBroadcasts);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred");
- }
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_LOCKED_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred LOCKED_BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_LOCKED_BOOT_COMPLETED));
- sb.append(" receivers");
- }
-
- n = getBootCompletedBroadcastsUidsSize(Intent.ACTION_BOOT_COMPLETED);
- if (n > 0) {
- sb.append(", ");
- sb.append(n);
- sb.append(" deferred BOOT_COMPLETED/");
- sb.append(getBootCompletedBroadcastsReceiversSize(Intent.ACTION_BOOT_COMPLETED));
- sb.append(" receivers");
- }
- return sb.toString();
- }
-
- // ----------------------------------
- // BroadcastQueue operation support
- void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- final ArrayList<BroadcastRecord> queue =
- (r.alarm && mQueue.mService.mConstants.mPrioritizeAlarmBroadcasts)
- ? mAlarmQueue
- : mOrderedBroadcasts;
-
- if (r.receivers == null || r.receivers.isEmpty()) {
- // Fast no-op path for broadcasts that won't actually be dispatched to
- // receivers - we still need to handle completion callbacks and historical
- // records, but we don't need to consider the fancy cases.
- queue.add(r);
- return;
- }
-
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_LOCKED_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(r.intent.getAction())) {
- // Create one BroadcastRecord for each UID that can be deferred.
- final SparseArray<BroadcastRecord> deferred =
- r.splitDeferredBootCompletedBroadcastLocked(mQueue.mService.mInternal,
- mQueue.mService.mConstants.mDeferBootCompletedBroadcast);
- getDeferredPerUser(r.userId).enqueueBootCompletedBroadcasts(
- Intent.ACTION_BOOT_COMPLETED, deferred);
- if (!r.receivers.isEmpty()) {
- // The non-deferred receivers.
- mOrderedBroadcasts.add(r);
- return;
- }
- } else {
- // Ordinary broadcast, so put it on the appropriate queue and carry on
- queue.add(r);
- }
- }
-
- /**
- * Return the total number of UIDs in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsUidsSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsUidsSize(action);
- }
- return size;
- }
-
- /**
- * Return the total number of receivers in all deferred boot completed BroadcastRecord.
- */
- private int getBootCompletedBroadcastsReceiversSize(String action) {
- int size = 0;
- for (int i = 0, s = mUser2Deferred.size(); i < s; i++) {
- size += mUser2Deferred.valueAt(i).getBootCompletedBroadcastsReceiversSize(action);
- }
- return size;
- }
-
- // Returns the now-replaced broadcast record, or null if none
- BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) {
- // Simple case, in the ordinary queue.
- BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging);
- // ... or possibly in the simple alarm queue
- if (old == null) {
- old = replaceBroadcastLocked(mAlarmQueue, r, typeForLogging);
- }
- // If we didn't find it, less-simple: in a deferral queue?
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mAlarmDeferrals, r, typeForLogging);
- }
- if (old == null) {
- old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging);
- }
- return old;
- }
-
- private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final int numEntries = list.size();
- for (int i = 0; i < numEntries; i++) {
- final Deferrals d = list.get(i);
- old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging);
- if (old != null) {
- return old;
- }
- }
- return null;
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list,
- BroadcastRecord r, String typeForLogging) {
- BroadcastRecord old;
- final Intent intent = r.intent;
- // Any in-flight broadcast has already been popped, and cannot be replaced.
- // (This preserves existing behavior of the replacement API)
- for (int i = list.size() - 1; i >= 0; i--) {
- old = list.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "***** Replacing " + typeForLogging
- + " [" + mQueue.mQueueName + "]: " + intent);
- }
- // Clone deferral state too if any
- r.deferred = old.deferred;
- list.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- boolean cleanupDisabledPackageReceiversLocked(final String packageName,
- Set<String> filterByClasses, final int userId, final boolean doit) {
- // Note: fast short circuits when 'doit' is false, as soon as we hit any
- // "yes we would do something" circumstance
- boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts,
- packageName, filterByClasses, userId, doit);
- if (doit || !didSomething) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(mAlarmQueue,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> lockedBootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredLockedBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- lockedBootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(
- lockedBootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- ArrayList<BroadcastRecord> bootCompletedBroadcasts = new ArrayList<>();
- for (int u = 0, usize = mUser2Deferred.size(); u < usize; u++) {
- SparseArray<BroadcastRecord> brs =
- mUser2Deferred.valueAt(u).mDeferredBootCompletedBroadcasts;
- for (int i = 0, size = brs.size(); i < size; i++) {
- bootCompletedBroadcasts.add(brs.valueAt(i));
- }
- }
- didSomething = cleanupBroadcastListDisabledReceiversLocked(bootCompletedBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmDeferrals,
- packageName, filterByClasses, userId, doit);
- }
- if (doit || !didSomething) {
- didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts,
- packageName, filterByClasses, userId, doit);
- }
- if ((doit || !didSomething) && mCurrentBroadcast != null) {
- didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, doit);
- }
-
- return didSomething;
- }
-
- private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (Deferrals d : list) {
- didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts,
- packageName, filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list,
- final String packageName, Set<String> filterByClasses, final int userId,
- final boolean doit) {
- boolean didSomething = false;
- for (BroadcastRecord br : list) {
- didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, doit);
- if (!doit && didSomething) {
- return true;
- }
- }
- return didSomething;
- }
-
- /**
- * Standard proto dump entry point
- */
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- if (mCurrentBroadcast != null) {
- mCurrentBroadcast.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- br.dumpDebug(proto, fieldId);
- }
- for (BroadcastRecord br : mAlarmQueue) {
- br.dumpDebug(proto, fieldId);
- }
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpDebug(proto, fieldId);
- }
-
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dumpDebug(proto, fieldId);
- }
- }
-
- // ----------------------------------
- // Dispatch & deferral management
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mCurrentBroadcast;
- }
-
- /**
- * If there is a deferred broadcast that is being sent to an alarm target, return
- * that one. If there's no deferred alarm target broadcast but there is one
- * that has reached the end of its deferral, return that.
- *
- * This stages the broadcast internally until it is retired, and returns that
- * staged record if this is called repeatedly, until retireBroadcast(r) is called.
- */
- public BroadcastRecord getNextBroadcastLocked(final long now) {
- if (mCurrentBroadcast != null) {
- return mCurrentBroadcast;
- }
-
- BroadcastRecord next = null;
-
- // Alarms in flight take precedence over everything else. This queue
- // will be non-empty only when the relevant policy is in force, but if
- // policy has changed on the fly we still need to drain this before we
- // settle into the legacy behavior.
- if (!mAlarmQueue.isEmpty()) {
- next = mAlarmQueue.remove(0);
- }
-
- // Next in precedence are deferred BOOT_COMPLETED broadcasts
- if (next == null) {
- next = dequeueDeferredBootCompletedBroadcast();
- }
-
- // Alarm-related deferrals are next in precedence...
- if (next == null && !mAlarmDeferrals.isEmpty()) {
- next = popLocked(mAlarmDeferrals);
- if (DEBUG_BROADCAST_DEFERRAL && next != null) {
- Slog.i(TAG, "Next broadcast from alarm targets: " + next);
- }
- }
-
- final boolean someQueued = !mOrderedBroadcasts.isEmpty();
-
- if (next == null && !mDeferredBroadcasts.isEmpty()) {
- // A this point we're going to deliver either:
- // 1. the next "overdue" deferral; or
- // 2. the next ordinary ordered broadcast; *or*
- // 3. the next not-yet-overdue deferral.
-
- for (int i = 0; i < mDeferredBroadcasts.size(); i++) {
- Deferrals d = mDeferredBroadcasts.get(i);
- if (now < d.deferUntil && someQueued) {
- // stop looking when we haven't hit the next time-out boundary
- // but only if we have un-deferred broadcasts waiting,
- // otherwise we can deliver whatever deferred broadcast
- // is next available.
- break;
- }
-
- if (d.broadcasts.size() > 0) {
- next = d.broadcasts.remove(0);
- // apply deferral-interval decay policy and move this uid's
- // deferred broadcasts down in the delivery queue accordingly
- mDeferredBroadcasts.remove(i); // already 'd'
- d.deferredBy = calculateDeferral(d.deferredBy);
- d.deferUntil += d.deferredBy;
- insertLocked(mDeferredBroadcasts, d);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from deferrals " + next
- + ", deferUntil now " + d.deferUntil);
- }
- break;
- }
- }
- }
-
- if (next == null && someQueued) {
- next = mOrderedBroadcasts.remove(0);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Next broadcast from main queue: " + next);
- }
- }
-
- mCurrentBroadcast = next;
- return next;
- }
-
- /**
- * Called after the broadcast queue finishes processing the currently
- * active broadcast (obtained by calling getNextBroadcastLocked()).
- */
- public void retireBroadcastLocked(final BroadcastRecord r) {
- // ERROR if 'r' is not the active broadcast
- if (r != mCurrentBroadcast) {
- Slog.wtf(TAG, "Retiring broadcast " + r
- + " doesn't match current outgoing " + mCurrentBroadcast);
- }
- mCurrentBroadcast = null;
- }
-
- /**
- * Called prior to broadcast dispatch to check whether the intended
- * recipient is currently subject to deferral policy.
- */
- public boolean isDeferringLocked(final int uid) {
- Deferrals d = findUidLocked(uid);
- if (d != null && d.broadcasts.isEmpty()) {
- // once we've caught up with deferred broadcasts to this uid
- // and time has advanced sufficiently that we wouldn't be
- // deferring newly-enqueued ones, we're back to normal policy.
- if (SystemClock.uptimeMillis() >= d.deferUntil) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid);
- }
- removeDeferral(d);
- return false;
- }
- }
- return (d != null);
- }
-
- /**
- * Defer broadcasts for the given app. If 'br' is non-null, this also makes
- * sure that broadcast record is enqueued as the next upcoming broadcast for
- * the app.
- */
- public void startDeferring(final int uid) {
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
-
- // If we're not yet tracking this app, set up that bookkeeping
- if (d == null) {
- // Start a new deferral
- final long now = SystemClock.uptimeMillis();
- d = new Deferrals(uid,
- now,
- mConstants.DEFERRAL,
- mAlarmUids.get(uid, 0));
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Now deferring broadcasts to " + uid
- + " until " + d.deferUntil);
- }
- // where it goes depends on whether it is coming into an alarm-related situation
- if (d.alarmCount == 0) {
- // common case, put it in the ordinary priority queue
- insertLocked(mDeferredBroadcasts, d);
- scheduleDeferralCheckLocked(true);
- } else {
- // alarm-related: strict order-encountered
- mAlarmDeferrals.add(d);
- }
- } else {
- // We're already deferring, but something was slow again. Reset the
- // deferral decay progression.
- d.deferredBy = mConstants.DEFERRAL;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to "
- + d.deferredBy);
- }
- }
- }
- }
-
- /**
- * Key entry point when a broadcast about to be delivered is instead
- * set aside for deferred delivery
- */
- public void addDeferredBroadcast(final int uid, BroadcastRecord br) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Enqueuing deferred broadcast " + br);
- }
- synchronized (mLock) {
- Deferrals d = findUidLocked(uid);
- if (d == null) {
- Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid);
- } else {
- if (br == null) {
- Slog.wtf(TAG, "Deferring null broadcast to " + uid);
- } else {
- br.deferred = true;
- d.add(br);
- }
- }
- }
- }
-
- /**
- * When there are deferred broadcasts, we need to make sure to recheck the
- * dispatch queue when they come due. Alarm-sensitive deferrals get dispatched
- * aggressively, so we only need to use the ordinary deferrals timing to figure
- * out when to recheck.
- */
- public void scheduleDeferralCheckLocked(boolean force) {
- if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) {
- final Deferrals d = mDeferredBroadcasts.get(0);
- if (!d.broadcasts.isEmpty()) {
- mHandler.removeCallbacks(mScheduleRunnable);
- mHandler.postAtTime(mScheduleRunnable, d.deferUntil);
- mRecheckScheduled = true;
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil);
- }
- }
- }
- }
-
- /**
- * Cancel all current deferrals; that is, make all currently-deferred broadcasts
- * immediately deliverable. Used by the wait-for-broadcast-idle mechanism.
- */
- public void cancelDeferralsLocked() {
- zeroDeferralTimes(mAlarmDeferrals);
- zeroDeferralTimes(mDeferredBroadcasts);
- }
-
- private static void zeroDeferralTimes(ArrayList<Deferrals> list) {
- final int num = list.size();
- for (int i = 0; i < num; i++) {
- Deferrals d = list.get(i);
- // Safe to do this in-place because it won't break ordering
- d.deferUntil = d.deferredBy = 0;
- }
- }
-
- // ----------------------------------
-
- /**
- * If broadcasts to this uid are being deferred, find the deferrals record about it.
- * @return null if this uid's broadcasts are not being deferred
- */
- private Deferrals findUidLocked(final int uid) {
- // The common case is that they it isn't also an alarm target...
- Deferrals d = findUidLocked(uid, mDeferredBroadcasts);
- // ...but if not there, also check alarm-prioritized deferrals
- if (d == null) {
- d = findUidLocked(uid, mAlarmDeferrals);
- }
- return d;
- }
-
- /**
- * Remove the given deferral record from whichever queue it might be in at present
- * @return true if the deferral was in fact found, false if this made no changes
- */
- private boolean removeDeferral(Deferrals d) {
- boolean didRemove = mDeferredBroadcasts.remove(d);
- if (!didRemove) {
- didRemove = mAlarmDeferrals.remove(d);
- }
- return didRemove;
- }
-
- /**
- * Find the deferrals record for the given uid in the given list
- */
- private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) {
- final int numElements = list.size();
- for (int i = 0; i < numElements; i++) {
- Deferrals d = list.get(i);
- if (uid == d.uid) {
- return d;
- }
- }
- return null;
- }
-
- /**
- * Pop the next broadcast record from the head of the given deferrals list,
- * if one exists.
- */
- private static BroadcastRecord popLocked(ArrayList<Deferrals> list) {
- final Deferrals d = list.get(0);
- return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0);
- }
-
- /**
- * Insert the given Deferrals into the priority queue, sorted by defer-until milestone
- */
- private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) {
- // Simple linear search is appropriate here because we expect to
- // have very few entries in the deferral lists (i.e. very few badly-
- // behaving apps currently facing deferral)
- int i;
- final int numElements = list.size();
- for (i = 0; i < numElements; i++) {
- if (d.deferUntil < list.get(i).deferUntil) {
- break;
- }
- }
- list.add(i, d);
- }
-
- /**
- * Calculate a new deferral time based on the previous time. This should decay
- * toward zero, though a small nonzero floor is an option.
- */
- private long calculateDeferral(long previous) {
- return Math.max(mConstants.DEFERRAL_FLOOR,
- (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR));
- }
-
- // ----------------------------------
-
- @NeverCompile
- boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName,
- SimpleDateFormat sdf) {
- final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf);
- boolean printed = false;
-
- dumper.setHeading("Currently in flight");
- dumper.setLabel("In-Flight Ordered Broadcast");
- if (mCurrentBroadcast != null) {
- dumper.dump(mCurrentBroadcast);
- } else {
- pw.println(" (null)");
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active alarm broadcasts");
- dumper.setLabel("Active Alarm Broadcast");
- for (BroadcastRecord br : mAlarmQueue) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Active ordered broadcasts");
- dumper.setLabel("Active Ordered Broadcast");
- for (Deferrals d : mAlarmDeferrals) {
- d.dumpLocked(dumper);
- }
- for (BroadcastRecord br : mOrderedBroadcasts) {
- dumper.dump(br);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred ordered broadcasts");
- dumper.setLabel("Deferred Ordered Broadcast");
- for (Deferrals d : mDeferredBroadcasts) {
- d.dumpLocked(dumper);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred LOCKED_BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred LOCKED_BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_LOCKED_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- dumper.setHeading("Deferred BOOT_COMPLETED broadcasts");
- dumper.setLabel("Deferred BOOT_COMPLETED Broadcast");
- for (int i = 0, size = mUser2Deferred.size(); i < size; i++) {
- mUser2Deferred.valueAt(i).dump(dumper, Intent.ACTION_BOOT_COMPLETED);
- }
- printed |= dumper.didPrint();
-
- return printed;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index d1c8c30..4a37913 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -276,6 +276,11 @@
&& record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
+ if (mLastDeferredStates && shouldBeDeferred()
+ && (record.getDeliveryState(recordIndex)
+ == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
return replacedBroadcastRecord;
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
deleted file mode 100644
index 3c56752..0000000
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ /dev/null
@@ -1,1983 +0,0 @@
-/*
- * Copyright (C) 2012 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.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
-import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
-import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
-import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
-import static android.text.TextUtils.formatSimple;
-
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
-import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ApplicationExitInfo;
-import android.app.BroadcastOptions;
-import android.app.IApplicationThread;
-import android.app.usage.UsageEvents.Event;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.IIntentReceiver;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.UserInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PowerExemptionManager.ReasonCode;
-import android.os.PowerExemptionManager.TempAllowListType;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.Slog;
-import android.util.SparseIntArray;
-import android.util.proto.ProtoOutputStream;
-
-import com.android.internal.os.TimeoutRecord;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserJourneyLogger;
-import com.android.server.pm.UserManagerInternal;
-
-import dalvik.annotation.optimization.NeverCompile;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Set;
-import java.util.function.BooleanSupplier;
-
-/**
- * BROADCASTS
- *
- * We keep three broadcast queues and associated bookkeeping, one for those at
- * foreground priority, and one for normal (background-priority) broadcasts, and one to
- * offload special broadcasts that we know take a long time, such as BOOT_COMPLETED.
- */
-public class BroadcastQueueImpl extends BroadcastQueue {
- private static final String TAG_MU = TAG + POSTFIX_MU;
- private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
-
- final BroadcastConstants mConstants;
-
- /**
- * If true, we can delay broadcasts while waiting services to finish in the previous
- * receiver's process.
- */
- final boolean mDelayBehindServices;
-
- final int mSchedGroup;
-
- /**
- * Lists of all active broadcasts that are to be executed immediately
- * (without waiting for another broadcast to finish). Currently this only
- * contains broadcasts to registered receivers, to avoid spinning up
- * a bunch of processes to execute IntentReceiver components. Background-
- * and foreground-priority broadcasts are queued separately.
- */
- final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
-
- /**
- * Tracking of the ordered broadcast queue, including deferral policy and alarm
- * prioritization.
- */
- final BroadcastDispatcher mDispatcher;
-
- /**
- * Refcounting for completion callbacks of split/deferred broadcasts. The key
- * is an opaque integer token assigned lazily when a broadcast is first split
- * into multiple BroadcastRecord objects.
- */
- final SparseIntArray mSplitRefcounts = new SparseIntArray();
- private int mNextToken = 0;
-
- /**
- * Set when we current have a BROADCAST_INTENT_MSG in flight.
- */
- boolean mBroadcastsScheduled = false;
-
- /**
- * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
- */
- boolean mPendingBroadcastTimeoutMessage;
-
- /**
- * Intent broadcasts that we have tried to start, but are
- * waiting for the application's process to be created. We only
- * need one per scheduling class (instead of a list) because we always
- * process broadcasts one at a time, so no others can be started while
- * waiting for this one.
- */
- BroadcastRecord mPendingBroadcast = null;
-
- /**
- * The receiver index that is pending, to restart the broadcast if needed.
- */
- int mPendingBroadcastRecvIndex;
-
- static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
- static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
-
- // log latency metrics for ordered broadcasts during BOOT_COMPLETED processing
- boolean mLogLatencyMetrics = true;
-
- final BroadcastHandler mHandler;
-
- private final class BroadcastHandler extends Handler {
- public BroadcastHandler(Looper looper) {
- super(looper, null);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case BROADCAST_INTENT_MSG: {
- if (DEBUG_BROADCAST) Slog.v(
- TAG_BROADCAST, "Received BROADCAST_INTENT_MSG ["
- + mQueueName + "]");
- processNextBroadcast(true);
- } break;
- case BROADCAST_TIMEOUT_MSG: {
- synchronized (mService) {
- broadcastTimeoutLocked(true);
- }
- } break;
- }
- }
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, boolean allowDelayBehindServices,
- int schedGroup) {
- this(service, handler, name, constants, new BroadcastSkipPolicy(service),
- new BroadcastHistory(constants), allowDelayBehindServices, schedGroup);
- }
-
- BroadcastQueueImpl(ActivityManagerService service, Handler handler,
- String name, BroadcastConstants constants, BroadcastSkipPolicy skipPolicy,
- BroadcastHistory history, boolean allowDelayBehindServices, int schedGroup) {
- super(service, handler, name, skipPolicy, history);
- mHandler = new BroadcastHandler(handler.getLooper());
- mConstants = constants;
- mDelayBehindServices = allowDelayBehindServices;
- mSchedGroup = schedGroup;
- mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService);
- }
-
- public void start(ContentResolver resolver) {
- mDispatcher.start();
- mConstants.startObserving(mHandler, resolver);
- }
-
- public boolean isDelayBehindServices() {
- return mDelayBehindServices;
- }
-
- public BroadcastRecord getPendingBroadcastLocked() {
- return mPendingBroadcast;
- }
-
- public BroadcastRecord getActiveBroadcastLocked() {
- return mDispatcher.getActiveBroadcastLocked();
- }
-
- public int getPreferredSchedulingGroupLocked(ProcessRecord app) {
- final BroadcastRecord active = getActiveBroadcastLocked();
- if (active != null && active.curApp == app) {
- return mSchedGroup;
- }
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if (pending != null && pending.curApp == app) {
- return mSchedGroup;
- }
- return ProcessList.SCHED_GROUP_UNDEFINED;
- }
-
- public void enqueueBroadcastLocked(BroadcastRecord r) {
- r.applySingletonPolicy(mService);
-
- final boolean replacePending = (r.intent.getFlags()
- & Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
-
- // Ordered broadcasts obviously need to be dispatched in serial order,
- // but this implementation expects all manifest receivers to also be
- // dispatched in a serial fashion
- boolean serialDispatch = r.ordered;
- if (!serialDispatch) {
- final int N = (r.receivers != null) ? r.receivers.size() : 0;
- for (int i = 0; i < N; i++) {
- if (r.receivers.get(i) instanceof ResolveInfo) {
- serialDispatch = true;
- break;
- }
- }
- }
-
- if (serialDispatch) {
- final BroadcastRecord oldRecord =
- replacePending ? replaceOrderedBroadcastLocked(r) : null;
- if (oldRecord != null) {
- // Replaced, fire the result-to receiver.
- if (oldRecord.resultTo != null) {
- try {
- oldRecord.mIsReceiverAppRunning = true;
- performReceiveLocked(oldRecord, oldRecord.resultToApp, oldRecord.resultTo,
- oldRecord.intent,
- Activity.RESULT_CANCELED, null, null,
- false, false, oldRecord.shareIdentity, oldRecord.userId,
- oldRecord.callingUid, r.callingUid, r.callerPackage,
- SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0, 0,
- oldRecord.resultToApp != null
- ? oldRecord.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + oldRecord.intent, e);
-
- }
- }
- } else {
- enqueueOrderedBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- } else {
- final boolean replaced = replacePending
- && (replaceParallelBroadcastLocked(r) != null);
- // Note: We assume resultTo is null for non-ordered broadcasts.
- if (!replaced) {
- enqueueParallelBroadcastLocked(r);
- scheduleBroadcastsLocked();
- }
- }
- }
-
- public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mParallelBroadcasts.add(r);
- enqueueBroadcastHelper(r);
- }
-
- public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
- r.enqueueClockTime = System.currentTimeMillis();
- r.enqueueTime = SystemClock.uptimeMillis();
- r.enqueueRealTime = SystemClock.elapsedRealtime();
- mDispatcher.enqueueOrderedBroadcastLocked(r);
- enqueueBroadcastHelper(r);
- }
-
- /**
- * Don't call this method directly; call enqueueParallelBroadcastLocked or
- * enqueueOrderedBroadcastLocked.
- */
- private void enqueueBroadcastHelper(BroadcastRecord r) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- }
- }
-
- /**
- * Find the same intent from queued parallel broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceParallelBroadcastLocked(BroadcastRecord r) {
- return replaceBroadcastLocked(mParallelBroadcasts, r, "PARALLEL");
- }
-
- /**
- * Find the same intent from queued ordered broadcast, replace with a new one and return
- * the old one.
- */
- public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) {
- return mDispatcher.replaceBroadcastLocked(r, "ORDERED");
- }
-
- private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
- BroadcastRecord r, String typeForLogging) {
- final Intent intent = r.intent;
- for (int i = queue.size() - 1; i >= 0; i--) {
- final BroadcastRecord old = queue.get(i);
- if (old.userId == r.userId && intent.filterEquals(old.intent)) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST, "***** DROPPING "
- + typeForLogging + " [" + mQueueName + "]: " + intent);
- }
- queue.set(i, r);
- return old;
- }
- }
- return null;
- }
-
- private final void processCurBroadcastLocked(BroadcastRecord r,
- ProcessRecord app) throws RemoteException {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " for app " + app);
- final IApplicationThread thread = app.getThread();
- if (thread == null) {
- throw new RemoteException();
- }
- if (app.isInFullBackup()) {
- skipReceiverLocked(r);
- return;
- }
-
- r.curApp = app;
- r.curAppLastProcessState = app.mState.getCurProcState();
- final ProcessReceiverRecord prr = app.mReceivers;
- prr.addCurReceiver(r);
- app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
- // Don't bump its LRU position if it's in the background restricted.
- if (mService.mInternal.getRestrictionLevel(app.info.packageName, app.userId)
- < RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
- mService.updateLruProcessLocked(app, false, null);
- }
- // Make sure the oom adj score is updated before delivering the broadcast.
- // Force an update, even if there are other pending requests, overall it still saves time,
- // because time(updateOomAdj(N apps)) <= N * time(updateOomAdj(1 app)).
- mService.enqueueOomAdjTargetLocked(app);
- mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
-
- // Tell the application to launch this receiver.
- maybeReportBroadcastDispatchedEventLocked(r, r.curReceiver.applicationInfo.uid);
- r.intent.setComponent(r.curComponent);
-
- // See if we need to delay the freezer based on BroadcastOptions
- if (r.options != null
- && r.options.getTemporaryAppAllowlistDuration() > 0
- && r.options.getTemporaryAppAllowlistType()
- == TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER,
- r.options.getTemporaryAppAllowlistDuration());
- }
-
- boolean started = false;
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Delivering to component " + r.curComponent
- + ": " + r);
- mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
- PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
- final boolean assumeDelivered = false;
- thread.scheduleReceiver(
- prepareReceiverIntent(r.intent, r.curFilteredExtras),
- r.curReceiver, null /* compatInfo (unused but need to keep method signature) */,
- r.resultCode, r.resultData, r.resultExtras, r.ordered, assumeDelivered,
- r.userId, r.shareIdentity ? r.callingUid : Process.INVALID_UID,
- app.mState.getReportedProcState(),
- r.shareIdentity ? r.callerPackage : null);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + " DELIVERED for app " + app);
- started = true;
- } finally {
- if (!started) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Process cur broadcast " + r + ": NOT STARTED!");
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- prr.removeCurReceiver(r);
- }
- }
-
- // if something bad happens here, launch the app and try again
- if (app.isKilled()) {
- throw new RemoteException("app gets killed during broadcasting");
- }
- }
-
- /**
- * Called by ActivityManagerService to notify that the uid has process started, if there is any
- * deferred BOOT_COMPLETED broadcast, the BroadcastDispatcher can dispatch the broadcast now.
- * @param uid
- */
- public void updateUidReadyForBootCompletedBroadcastLocked(int uid) {
- mDispatcher.updateUidReadyForBootCompletedBroadcastLocked(uid);
- scheduleBroadcastsLocked();
- }
-
- public boolean onApplicationAttachedLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- updateUidReadyForBootCompletedBroadcastLocked(app.uid);
-
- if (mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- return sendPendingBroadcastsLocked(app);
- } else {
- return false;
- }
- }
-
- public void onApplicationTimeoutLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationProblemLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onApplicationCleanupLocked(ProcessRecord app) {
- skipCurrentOrPendingReceiverLocked(app);
- }
-
- public void onProcessFreezableChangedLocked(ProcessRecord app) {
- // Not supported; ignore
- }
-
- public boolean sendPendingBroadcastsLocked(ProcessRecord app)
- throws BroadcastDeliveryFailedException {
- boolean didSomething = false;
- final BroadcastRecord br = mPendingBroadcast;
- if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
- if (br.curApp != app) {
- Slog.e(TAG, "App mismatch when sending pending broadcast to "
- + app.processName + ", intended target is " + br.curApp.processName);
- return false;
- }
- try {
- mPendingBroadcast = null;
- br.mIsReceiverAppRunning = false;
- processCurBroadcastLocked(br, app);
- didSomething = true;
- } catch (Exception e) {
- Slog.w(TAG, "Exception in new application when starting receiver "
- + br.curComponent.flattenToShortString(), e);
- logBroadcastReceiverDiscardLocked(br);
- finishReceiverLocked(br, br.resultCode, br.resultData,
- br.resultExtras, br.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- br.state = BroadcastRecord.IDLE;
- throw new BroadcastDeliveryFailedException(e);
- }
- }
- return didSomething;
- }
-
- // Skip the current receiver, if any, that is in flight to the given process
- public boolean skipCurrentOrPendingReceiverLocked(ProcessRecord app) {
- BroadcastRecord r = null;
- final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked();
- if (curActive != null && curActive.curApp == app) {
- // confirmed: the current active broadcast is to the given app
- r = curActive;
- }
-
- // If the current active broadcast isn't this BUT we're waiting for
- // mPendingBroadcast to spin up the target app, that's what we use.
- if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "[" + mQueueName + "] skip & discard pending app " + r);
- r = mPendingBroadcast;
- }
-
- if (r != null) {
- skipReceiverLocked(r);
- return true;
- } else {
- return false;
- }
- }
-
- private void skipReceiverLocked(BroadcastRecord r) {
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- }
-
- public void scheduleBroadcastsLocked() {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
- + mQueueName + "]: current="
- + mBroadcastsScheduled);
-
- if (mBroadcastsScheduled) {
- return;
- }
- mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
- mBroadcastsScheduled = true;
- }
-
- public BroadcastRecord getMatchingOrderedReceiver(ProcessRecord app) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br == null) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] no active broadcast");
- return null;
- }
- if (br.curApp != app) {
- Slog.w(TAG_BROADCAST, "getMatchingOrderedReceiver [" + mQueueName
- + "] active broadcast " + br.curApp + " doesn't match " + app);
- return null;
- }
- return br;
- }
-
- // > 0 only, no worry about "eventual" recycling
- private int nextSplitTokenLocked() {
- int next = mNextToken + 1;
- if (next <= 0) {
- next = 1;
- }
- mNextToken = next;
- return next;
- }
-
- private void postActivityStartTokenRemoval(ProcessRecord app, BroadcastRecord r) {
- // the receiver had run for less than allowed bg activity start timeout,
- // so allow the process to still start activities from bg for some more time
- String msgToken = (app.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then schedule the removal of the token after the extended timeout
- mHandler.postAtTime(() -> {
- synchronized (mService) {
- app.removeBackgroundStartPrivileges(r);
- }
- }, msgToken, (r.receiverTime + mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT));
- }
-
- public boolean finishReceiverLocked(ProcessRecord app, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final BroadcastRecord r = getMatchingOrderedReceiver(app);
- if (r != null) {
- return finishReceiverLocked(r, resultCode,
- resultData, resultExtras, resultAbort, waitForServices);
- } else {
- return false;
- }
- }
-
- public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
- String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
- final int state = r.state;
- final ActivityInfo receiver = r.curReceiver;
- final long finishTime = SystemClock.uptimeMillis();
- final long elapsed = finishTime - r.receiverTime;
- r.state = BroadcastRecord.IDLE;
- final int curIndex = r.nextReceiver - 1;
-
- final int packageState = r.mWasReceiverAppStopped
- ? SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED
- : SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
-
- if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
- final Object curReceiver = r.receivers.get(curIndex);
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED, r.curApp.uid,
- r.callingUid == -1 ? Process.SYSTEM_UID : r.callingUid,
- r.intent.getAction(),
- curReceiver instanceof BroadcastFilter
- ? BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME
- : BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST,
- r.mIsReceiverAppRunning
- ? BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM
- : BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime,
- finishTime - r.receiverTime,
- packageState,
- r.curApp.info.packageName,
- r.callerPackage,
- r.calculateTypeForLogging(),
- r.getDeliveryGroupPolicy(),
- r.intent.getFlags(),
- BroadcastRecord.getReceiverPriority(curReceiver),
- r.callerProcState,
- r.curAppLastProcessState);
- }
- if (state == BroadcastRecord.IDLE) {
- Slog.w(TAG_BROADCAST, "finishReceiver [" + mQueueName + "] called but state is IDLE");
- }
- if (r.mBackgroundStartPrivileges.allowsAny() && r.curApp != null) {
- if (elapsed > mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT) {
- // if the receiver has run for more than allowed bg activity start timeout,
- // just remove the token for this process now and we're done
- r.curApp.removeBackgroundStartPrivileges(r);
- } else {
- // It gets more time; post the removal to happen at the appropriate moment
- postActivityStartTokenRemoval(r.curApp, r);
- }
- }
- // If we're abandoning this broadcast before any receivers were actually spun up,
- // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply.
- if (r.nextReceiver > 0) {
- r.terminalTime[r.nextReceiver - 1] = finishTime;
- }
-
- // if this receiver was slow, impose deferral policy on the app. This will kick in
- // when processNextBroadcastLocked() next finds this uid as a receiver identity.
- if (!r.timeoutExempt) {
- // r.curApp can be null if finish has raced with process death - benign
- // edge case, and we just ignore it because we're already cleaning up
- // as expected.
- if (r.curApp != null
- && mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) {
- // Core system packages are exempt from deferral policy
- if (!UserHandle.isCore(r.curApp.uid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Broadcast receiver " + (r.nextReceiver - 1)
- + " was slow: " + receiver + " br=" + r);
- }
- mDispatcher.startDeferring(r.curApp.uid);
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Core uid " + r.curApp.uid
- + " receiver was slow but not deferring: "
- + receiver + " br=" + r);
- }
- }
- }
- } else {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Finished broadcast " + r.intent.getAction()
- + " is exempt from deferral policy");
- }
- }
-
- r.intent.setComponent(null);
- if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
- r.curApp.mReceivers.removeCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- if (r.curFilter != null) {
- r.curFilter.receiverList.curBroadcast = null;
- }
- r.curFilter = null;
- r.curReceiver = null;
- r.curApp = null;
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_UNKNOWN;
- r.curFilteredExtras = null;
- r.mWasReceiverAppStopped = false;
- mPendingBroadcast = null;
-
- r.resultCode = resultCode;
- r.resultData = resultData;
- r.resultExtras = resultExtras;
- if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
- r.resultAbort = resultAbort;
- } else {
- r.resultAbort = false;
- }
-
- // If we want to wait behind services *AND* we're finishing the head/
- // active broadcast on its queue
- if (waitForServices && r.curComponent != null && r.queue.isDelayBehindServices()
- && ((BroadcastQueueImpl) r.queue).getActiveBroadcastLocked() == r) {
- ActivityInfo nextReceiver;
- if (r.nextReceiver < r.receivers.size()) {
- Object obj = r.receivers.get(r.nextReceiver);
- nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
- } else {
- nextReceiver = null;
- }
- // Don't do this if the next receive is in the same process as the current one.
- if (receiver == null || nextReceiver == null
- || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
- || !receiver.processName.equals(nextReceiver.processName)) {
- // In this case, we are ready to process the next receiver for the current broadcast,
- // but are on a queue that would like to wait for services to finish before moving
- // on. If there are background services currently starting, then we will go into a
- // special state where we hold off on continuing this broadcast until they are done.
- if (mService.mServices.hasBackgroundServicesLocked(r.userId)) {
- Slog.i(TAG, "Delay finish: " + r.curComponent.flattenToShortString());
- r.state = BroadcastRecord.WAITING_SERVICES;
- return false;
- }
- }
- }
-
- r.curComponent = null;
-
- // We will process the next receiver right now if this is finishing
- // an app receiver (which is always asynchronous) or after we have
- // come back from calling a receiver.
- final boolean doNext = (state == BroadcastRecord.APP_RECEIVE)
- || (state == BroadcastRecord.CALL_DONE_RECEIVE);
- if (doNext) {
- processNextBroadcastLocked(/* fromMsg= */ false, /* skipOomAdj= */ true);
- }
- return doNext;
- }
-
- public void backgroundServicesFinishedLocked(int userId) {
- BroadcastRecord br = mDispatcher.getActiveBroadcastLocked();
- if (br != null) {
- if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
- Slog.i(TAG, "Resuming delayed broadcast");
- br.curComponent = null;
- br.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- }
- }
- }
-
- public void performReceiveLocked(BroadcastRecord r, ProcessRecord app, IIntentReceiver receiver,
- Intent intent, int resultCode, String data, Bundle extras,
- boolean ordered, boolean sticky, boolean shareIdentity, int sendingUser,
- int receiverUid, int callingUid, String callingPackage,
- long dispatchDelay, long receiveDelay, int priority,
- int receiverProcessState) throws RemoteException {
- // If the broadcaster opted-in to sharing their identity, then expose package visibility for
- // the receiver.
- if (shareIdentity) {
- mService.mPackageManagerInt.grantImplicitAccess(sendingUser, intent,
- UserHandle.getAppId(receiverUid), callingUid, true);
- }
- // Send the intent to the receiver asynchronously using one-way binder calls.
- if (app != null) {
- final IApplicationThread thread = app.getThread();
- if (thread != null) {
- // If we have an app thread, do the call through that so it is
- // correctly ordered with other one-way calls.
- try {
- final boolean assumeDelivered = !ordered;
- thread.scheduleRegisteredReceiver(
- receiver, intent, resultCode,
- data, extras, ordered, sticky, assumeDelivered, sendingUser,
- app.mState.getReportedProcState(),
- shareIdentity ? callingUid : Process.INVALID_UID,
- shareIdentity ? callingPackage : null);
- } catch (RemoteException ex) {
- // Failed to call into the process. It's either dying or wedged. Kill it gently.
- synchronized (mService) {
- final String msg = "Failed to schedule " + intent + " to " + receiver
- + " via " + app + ": " + ex;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- }
- throw ex;
- }
- } else {
- // Application has died. Receiver doesn't exist.
- throw new RemoteException("app.thread must not be null");
- }
- } else {
- receiver.performReceive(intent, resultCode, data, extras, ordered,
- sticky, sendingUser);
- }
- if (!ordered) {
- FrameworkStatsLog.write(BROADCAST_DELIVERY_EVENT_REPORTED,
- receiverUid == -1 ? Process.SYSTEM_UID : receiverUid,
- callingUid == -1 ? Process.SYSTEM_UID : callingUid,
- intent.getAction(),
- BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME,
- BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM,
- dispatchDelay, receiveDelay, 0 /* finish_delay */,
- SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL,
- app != null ? app.info.packageName : null, callingPackage,
- r.calculateTypeForLogging(), r.getDeliveryGroupPolicy(), r.intent.getFlags(),
- priority, r.callerProcState, receiverProcessState);
- }
- }
-
- private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
- BroadcastFilter filter, boolean ordered, int index) {
- boolean skip = mSkipPolicy.shouldSkip(r, filter);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(filter.receiverList.uid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + filter.receiverList.app
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
- return;
- }
-
- r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
-
- // If this is not being sent as an ordered broadcast, then we
- // don't want to touch the fields that keep track of the current
- // state of ordered broadcasts.
- if (ordered) {
- r.curFilter = filter;
- filter.receiverList.curBroadcast = r;
- r.state = BroadcastRecord.CALL_IN_RECEIVE;
- if (filter.receiverList.app != null) {
- // Bump hosting application to no longer be in background
- // scheduling class. Note that we can't do that if there
- // isn't an app... but we can only be in that case for
- // things that directly call the IActivityManager API, which
- // are already core system stuff so don't matter for this.
- r.curApp = filter.receiverList.app;
- r.curAppLastProcessState = r.curApp.mState.getCurProcState();
- filter.receiverList.app.mReceivers.addCurReceiver(r);
- mService.enqueueOomAdjTargetLocked(r.curApp);
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
- } else if (filter.receiverList.app != null) {
- mService.mOomAdjuster.unfreezeTemporarily(filter.receiverList.app,
- CachedAppOptimizer.UNFREEZE_REASON_START_RECEIVER);
- }
-
- try {
- if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
- "Delivering to " + filter + " : " + r);
- final boolean isInFullBackup = (filter.receiverList.app != null)
- && filter.receiverList.app.isInFullBackup();
- final boolean isKilled = (filter.receiverList.app != null)
- && filter.receiverList.app.isKilled();
- if (isInFullBackup || isKilled) {
- // Skip delivery if full backup in progress
- // If it's an ordered broadcast, we need to continue to the next receiver.
- if (ordered) {
- skipReceiverLocked(r);
- }
- } else {
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[index] = r.receiverTime;
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
- maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
- performReceiveLocked(r, filter.receiverList.app, filter.receiverList.receiver,
- prepareReceiverIntent(r.intent, filteredExtras), r.resultCode, r.resultData,
- r.resultExtras, r.ordered, r.initialSticky, r.shareIdentity, r.userId,
- filter.receiverList.uid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- r.receiverTime - r.dispatchTime, filter.getPriority(),
- filter.receiverList.app != null
- ? filter.receiverList.app.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- // parallel broadcasts are fire-and-forget, not bookended by a call to
- // finishReceiverLocked(), so we manage their activity-start token here
- if (filter.receiverList.app != null
- && r.mBackgroundStartPrivileges.allowsAny()
- && !r.ordered) {
- postActivityStartTokenRemoval(filter.receiverList.app, r);
- }
- }
- if (ordered) {
- r.state = BroadcastRecord.CALL_DONE_RECEIVE;
- }
- } catch (RemoteException e) {
- Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
- // Clean up ProcessRecord state related to this broadcast attempt
- if (filter.receiverList.app != null) {
- filter.receiverList.app.removeBackgroundStartPrivileges(r);
- if (ordered) {
- filter.receiverList.app.mReceivers.removeCurReceiver(r);
- // Something wrong, its oom adj could be downgraded, but not in a hurry.
- mService.enqueueOomAdjTargetLocked(r.curApp);
- }
- }
- // And BroadcastRecord state related to ordered delivery, if appropriate
- if (ordered) {
- r.curFilter = null;
- filter.receiverList.curBroadcast = null;
- }
- }
- }
-
- void maybeScheduleTempAllowlistLocked(int uid, BroadcastRecord r,
- @Nullable BroadcastOptions brOptions) {
- if (brOptions == null || brOptions.getTemporaryAppAllowlistDuration() <= 0) {
- return;
- }
- long duration = brOptions.getTemporaryAppAllowlistDuration();
- final @TempAllowListType int type = brOptions.getTemporaryAppAllowlistType();
- final @ReasonCode int reasonCode = brOptions.getTemporaryAppAllowlistReasonCode();
- final String reason = brOptions.getTemporaryAppAllowlistReason();
-
- if (duration > Integer.MAX_VALUE) {
- duration = Integer.MAX_VALUE;
- }
- // XXX ideally we should pause the broadcast until everything behind this is done,
- // or else we will likely start dispatching the broadcast before we have opened
- // access to the app (there is a lot of asynchronicity behind this). It is probably
- // not that big a deal, however, because the main purpose here is to allow apps
- // to hold wake locks, and they will be able to acquire their wake lock immediately
- // it just won't be enabled until we get through this work.
- StringBuilder b = new StringBuilder();
- b.append("broadcast:");
- UserHandle.formatUid(b, r.callingUid);
- b.append(":");
- if (r.intent.getAction() != null) {
- b.append(r.intent.getAction());
- } else if (r.intent.getComponent() != null) {
- r.intent.getComponent().appendShortString(b);
- } else if (r.intent.getData() != null) {
- b.append(r.intent.getData());
- }
- b.append(",reason:");
- b.append(reason);
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Broadcast temp allowlist uid=" + uid + " duration=" + duration
- + " type=" + type + " : " + b.toString());
- }
-
- // Only add to temp allowlist if it's not the APP_FREEZING_DELAYED type. That will be
- // handled when the broadcast is actually being scheduled on the app thread.
- if (type != TEMPORARY_ALLOW_LIST_TYPE_APP_FREEZING_DELAYED) {
- mService.tempAllowlistUidLocked(uid, duration, reasonCode, b.toString(), type,
- r.callingUid);
- }
- }
-
- private void processNextBroadcast(boolean fromMsg) {
- synchronized (mService) {
- processNextBroadcastLocked(fromMsg, false);
- }
- }
-
- private static Intent prepareReceiverIntent(@NonNull Intent originalIntent,
- @Nullable Bundle filteredExtras) {
- final Intent intent = new Intent(originalIntent);
- if (filteredExtras != null) {
- intent.replaceExtras(filteredExtras);
- }
- return intent;
- }
-
- public void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
- BroadcastRecord r;
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast ["
- + mQueueName + "]: "
- + mParallelBroadcasts.size() + " parallel broadcasts; "
- + mDispatcher.describeStateLocked());
-
- mService.updateCpuStats();
-
- if (fromMsg) {
- mBroadcastsScheduled = false;
- }
-
- // First, deliver any non-serialized broadcasts right away.
- while (mParallelBroadcasts.size() > 0) {
- r = mParallelBroadcasts.remove(0);
- r.dispatchTime = SystemClock.uptimeMillis();
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
- r.mIsReceiverAppRunning = true;
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
-
- final int N = r.receivers.size();
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
- + mQueueName + "] " + r);
- for (int i=0; i<N; i++) {
- Object target = r.receivers.get(i);
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering non-ordered on [" + mQueueName + "] to registered "
- + target + ": " + r);
- deliverToRegisteredReceiverLocked(r,
- (BroadcastFilter) target, false, i);
- }
- addBroadcastToHistoryLocked(r);
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Done with parallel broadcast ["
- + mQueueName + "] " + r);
- }
-
- // Now take care of the next serialized one...
-
- // If we are waiting for a process to come up to handle the next
- // broadcast, then do nothing at this point. Just in case, we
- // check that the process we're waiting for still exists.
- if (mPendingBroadcast != null) {
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "processNextBroadcast [" + mQueueName + "]: waiting for "
- + mPendingBroadcast.curApp);
-
- boolean isDead;
- if (mPendingBroadcast.curApp.getPid() > 0) {
- synchronized (mService.mPidsSelfLocked) {
- ProcessRecord proc = mService.mPidsSelfLocked.get(
- mPendingBroadcast.curApp.getPid());
- isDead = proc == null || proc.mErrorState.isCrashing();
- }
- } else {
- final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
- mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
- isDead = proc == null || !proc.isPendingStart();
- }
- if (!isDead) {
- // It's still alive, so keep waiting
- return;
- } else {
- Slog.w(TAG, "pending app ["
- + mQueueName + "]" + mPendingBroadcast.curApp
- + " died before responding to broadcast");
- mPendingBroadcast.state = BroadcastRecord.IDLE;
- mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
- mPendingBroadcast = null;
- }
- }
-
- boolean looped = false;
-
- do {
- final long now = SystemClock.uptimeMillis();
- r = mDispatcher.getNextBroadcastLocked(now);
-
- if (r == null) {
- // No more broadcasts are deliverable right now, so all done!
- mDispatcher.scheduleDeferralCheckLocked(false);
- synchronized (mService.mAppProfiler.mProfilerLock) {
- mService.mAppProfiler.scheduleAppGcsLPf();
- }
- if (looped && !skipOomAdj) {
- // If we had finished the last ordered broadcast, then
- // make sure all processes have correct oom and sched
- // adjustments.
- mService.updateOomAdjPendingTargetsLocked(
- OOM_ADJ_REASON_START_RECEIVER);
- }
-
- // when we have no more ordered broadcast on this queue, stop logging
- if (mService.mUserController.mBootCompleted && mLogLatencyMetrics) {
- mLogLatencyMetrics = false;
- }
-
- return;
- }
-
- boolean forceReceive = false;
-
- // Ensure that even if something goes awry with the timeout
- // detection, we catch "hung" broadcasts here, discard them,
- // and continue to make progress.
- //
- // This is only done if the system is ready so that early-stage receivers
- // don't get executed with timeouts; and of course other timeout-
- // exempt broadcasts are ignored.
- int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
- if ((numReceivers > 0) &&
- (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
- Slog.w(TAG, "Hung broadcast ["
- + mQueueName + "] discarded after timeout failure:"
- + " now=" + now
- + " dispatchTime=" + r.dispatchTime
- + " startTime=" + r.receiverTime
- + " intent=" + r.intent
- + " numReceivers=" + numReceivers
- + " nextReceiver=" + r.nextReceiver
- + " state=" + r.state);
- broadcastTimeoutLocked(false); // forcibly finish this broadcast
- forceReceive = true;
- r.state = BroadcastRecord.IDLE;
- }
- }
-
- if (r.state != BroadcastRecord.IDLE) {
- if (DEBUG_BROADCAST) Slog.d(TAG_BROADCAST,
- "processNextBroadcast("
- + mQueueName + ") called when not idle (state="
- + r.state + ")");
- return;
- }
-
- // Is the current broadcast is done for any reason?
- if (r.receivers == null || r.nextReceiver >= numReceivers
- || r.resultAbort || forceReceive) {
- // Send the final result if requested
- if (r.resultTo != null) {
- boolean sendResult = true;
-
- // if this was part of a split/deferral complex, update the refcount and only
- // send the completion when we clear all of them
- if (r.splitToken != 0) {
- int newCount = mSplitRefcounts.get(r.splitToken) - 1;
- if (newCount == 0) {
- // done! clear out this record's bookkeeping and deliver
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Sending broadcast completion for split token "
- + r.splitToken + " : " + r.intent.getAction());
- }
- mSplitRefcounts.delete(r.splitToken);
- } else {
- // still have some split broadcast records in flight; update refcount
- // and hold off on the callback
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Result refcount now " + newCount + " for split token "
- + r.splitToken + " : " + r.intent.getAction()
- + " - not sending completion yet");
- }
- sendResult = false;
- mSplitRefcounts.put(r.splitToken, newCount);
- }
- }
- if (sendResult) {
- if (r.callerApp != null) {
- mService.mOomAdjuster.unfreezeTemporarily(
- r.callerApp,
- CachedAppOptimizer.UNFREEZE_REASON_FINISH_RECEIVER);
- }
- try {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] "
- + r.intent.getAction() + " app=" + r.callerApp);
- }
- if (r.dispatchTime == 0) {
- // The dispatch time here could be 0, in case it's a parallel
- // broadcast but it has a result receiver. Set it to now.
- r.dispatchTime = now;
- }
- r.mIsReceiverAppRunning = true;
- performReceiveLocked(r, r.resultToApp, r.resultTo,
- new Intent(r.intent), r.resultCode,
- r.resultData, r.resultExtras, false, false, r.shareIdentity,
- r.userId, r.callingUid, r.callingUid, r.callerPackage,
- r.dispatchTime - r.enqueueTime,
- now - r.dispatchTime, 0,
- r.resultToApp != null
- ? r.resultToApp.mState.getCurProcState()
- : ActivityManager.PROCESS_STATE_UNKNOWN);
- logBootCompletedBroadcastCompletionLatencyIfPossible(r);
- // Set this to null so that the reference
- // (local and remote) isn't kept in the mBroadcastHistory.
- r.resultTo = null;
- } catch (RemoteException e) {
- r.resultTo = null;
- Slog.w(TAG, "Failure ["
- + mQueueName + "] sending broadcast result of "
- + r.intent, e);
- }
- }
- }
-
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Cancelling BROADCAST_TIMEOUT_MSG");
- cancelBroadcastTimeoutLocked();
-
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST,
- "Finished with ordered broadcast " + r);
-
- // ... and on to the next...
- addBroadcastToHistoryLocked(r);
- if (r.intent.getComponent() == null && r.intent.getPackage() == null
- && (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
- // This was an implicit broadcast... let's record it for posterity.
- mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,
- r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);
- }
- mDispatcher.retireBroadcastLocked(r);
- r = null;
- looped = true;
- continue;
- }
-
- // Check whether the next receiver is under deferral policy, and handle that
- // accordingly. If the current broadcast was already part of deferred-delivery
- // tracking, we know that it must now be deliverable as-is without re-deferral.
- if (!r.deferred) {
- final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
- if (mDispatcher.isDeferringLocked(receiverUid)) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid
- + " at " + r.nextReceiver + " is under deferral");
- }
- // If this is the only (remaining) receiver in the broadcast, "splitting"
- // doesn't make sense -- just defer it as-is and retire it as the
- // currently active outgoing broadcast.
- BroadcastRecord defer;
- if (r.nextReceiver + 1 == numReceivers) {
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Sole receiver of " + r
- + " is under deferral; setting aside and proceeding");
- }
- defer = r;
- mDispatcher.retireBroadcastLocked(r);
- } else {
- // Nontrivial case; split out 'uid's receivers to a new broadcast record
- // and defer that, then loop and pick up continuing delivery of the current
- // record (now absent those receivers).
-
- // The split operation is guaranteed to match at least at 'nextReceiver'
- defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "Post split:");
- Slog.i(TAG_BROADCAST, "Original broadcast receivers:");
- for (int i = 0; i < r.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + r.receivers.get(i));
- }
- Slog.i(TAG_BROADCAST, "Split receivers:");
- for (int i = 0; i < defer.receivers.size(); i++) {
- Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i));
- }
- }
- // Track completion refcount as well if relevant
- if (r.resultTo != null) {
- int token = r.splitToken;
- if (token == 0) {
- // first split of this record; refcount for 'r' and 'deferred'
- r.splitToken = defer.splitToken = nextSplitTokenLocked();
- mSplitRefcounts.put(r.splitToken, 2);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST,
- "Broadcast needs split refcount; using new token "
- + r.splitToken);
- }
- } else {
- // new split from an already-refcounted situation; increment count
- final int curCount = mSplitRefcounts.get(token);
- if (DEBUG_BROADCAST_DEFERRAL) {
- if (curCount == 0) {
- Slog.wtf(TAG_BROADCAST,
- "Split refcount is zero with token for " + r);
- }
- }
- mSplitRefcounts.put(token, curCount + 1);
- if (DEBUG_BROADCAST_DEFERRAL) {
- Slog.i(TAG_BROADCAST, "New split count for token " + token
- + " is " + (curCount + 1));
- }
- }
- }
- }
- mDispatcher.addDeferredBroadcast(receiverUid, defer);
- r = null;
- looped = true;
- continue;
- }
- }
- } while (r == null);
-
- // Get the next receiver...
- int recIdx = r.nextReceiver++;
-
- // Keep track of when this receiver started, and make sure there
- // is a timeout message pending to kill it if need be.
- r.receiverTime = SystemClock.uptimeMillis();
- r.scheduledTime[recIdx] = r.receiverTime;
- if (recIdx == 0) {
- r.dispatchTime = r.receiverTime;
- r.dispatchRealTime = SystemClock.elapsedRealtime();
- r.dispatchClockTime = System.currentTimeMillis();
-
- if (mLogLatencyMetrics) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.BROADCAST_DISPATCH_LATENCY_REPORTED,
- r.dispatchClockTime - r.enqueueClockTime);
- }
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
- System.identityHashCode(r));
- Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(r));
- }
- if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing ordered broadcast ["
- + mQueueName + "] " + r);
- }
- if (! mPendingBroadcastTimeoutMessage) {
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Submitting BROADCAST_TIMEOUT_MSG ["
- + mQueueName + "] for " + r + " at " + timeoutTime);
- setBroadcastTimeoutLocked(timeoutTime);
- }
-
- final BroadcastOptions brOptions = r.options;
- final Object nextReceiver = r.receivers.get(recIdx);
-
- if (nextReceiver instanceof BroadcastFilter) {
- // Simple case: this is a registered receiver who gets
- // a direct call.
- BroadcastFilter filter = (BroadcastFilter)nextReceiver;
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Delivering ordered ["
- + mQueueName + "] to registered "
- + filter + ": " + r);
- r.mIsReceiverAppRunning = true;
- deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
- if ((r.curReceiver == null && r.curFilter == null) || !r.ordered) {
- // The receiver has already finished, so schedule to
- // process the next one.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
- + mQueueName + "]: ordered=" + r.ordered
- + " curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver);
- r.state = BroadcastRecord.IDLE;
- scheduleBroadcastsLocked();
- } else {
- if (filter.receiverList != null) {
- maybeAddBackgroundStartPrivileges(filter.receiverList.app, r);
- // r is guaranteed ordered at this point, so we know finishReceiverLocked()
- // will get a callback and handle the activity start token lifecycle.
- }
- }
- return;
- }
-
- // Hard case: need to instantiate the receiver, possibly
- // starting its application process to host it.
-
- final ResolveInfo info =
- (ResolveInfo)nextReceiver;
- final ComponentName component = new ComponentName(
- info.activityInfo.applicationInfo.packageName,
- info.activityInfo.name);
- final int receiverUid = info.activityInfo.applicationInfo.uid;
-
- final String targetProcess = info.activityInfo.processName;
- final ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
- info.activityInfo.applicationInfo.uid);
-
- boolean skip = mSkipPolicy.shouldSkip(r, info);
-
- // Filter packages in the intent extras, skipping delivery if none of the packages is
- // visible to the receiver.
- Bundle filteredExtras = null;
- if (!skip && r.filterExtrasForReceiver != null) {
- final Bundle extras = r.intent.getExtras();
- if (extras != null) {
- filteredExtras = r.filterExtrasForReceiver.apply(receiverUid, extras);
- if (filteredExtras == null) {
- if (DEBUG_BROADCAST) {
- Slog.v(TAG, "Skipping delivery to "
- + info.activityInfo.packageName + " / " + receiverUid
- + " : receiver is filtered by the package visibility");
- }
- skip = true;
- }
- }
- }
-
- if (skip) {
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Skipping delivery of ordered [" + mQueueName + "] "
- + r + " for reason described above");
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
- r.curFilter = null;
- r.state = BroadcastRecord.IDLE;
- r.manifestSkipCount++;
- scheduleBroadcastsLocked();
- return;
- }
- r.manifestCount++;
-
- r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;
- r.state = BroadcastRecord.APP_RECEIVE;
- r.curComponent = component;
- r.curReceiver = info.activityInfo;
- r.curFilteredExtras = filteredExtras;
- if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
- Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
- + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
- + receiverUid);
- }
- final boolean isActivityCapable =
- (brOptions != null && brOptions.getTemporaryAppAllowlistDuration() > 0);
- maybeScheduleTempAllowlistLocked(receiverUid, r, brOptions);
-
- // Report that a component is used for explicit broadcasts.
- if (r.intent.getComponent() != null && r.curComponent != null
- && !TextUtils.equals(r.curComponent.getPackageName(), r.callerPackage)) {
- mService.mUsageStatsService.reportEvent(
- r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
- }
-
- try {
- mService.mPackageManagerInt.notifyComponentUsed(
- r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + r.curComponent.getPackageName() + ": " + e);
- }
-
- // Is this receiver's application already running?
- if (app != null && app.getThread() != null && !app.isKilled()) {
- try {
- app.addPackage(info.activityInfo.packageName,
- info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
- maybeAddBackgroundStartPrivileges(app, r);
- r.mIsReceiverAppRunning = true;
- processCurBroadcastLocked(r, app);
- return;
- } catch (RemoteException e) {
- final String msg = "Failed to schedule " + r.intent + " to " + info
- + " via " + app + ": " + e;
- Slog.w(TAG, msg);
- app.killLocked("Can't deliver broadcast", ApplicationExitInfo.REASON_OTHER,
- ApplicationExitInfo.SUBREASON_UNDELIVERED_BROADCAST, true);
- } catch (RuntimeException e) {
- Slog.wtf(TAG, "Failed sending broadcast to "
- + r.curComponent + " with " + r.intent, e);
- // If some unexpected exception happened, just skip
- // this broadcast. At this point we are not in the call
- // from a client, so throwing an exception out from here
- // will crash the entire system instead of just whoever
- // sent the broadcast.
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- // We need to reset the state if we failed to start the receiver.
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- // If a dead object exception was thrown -- fall through to
- // restart the application.
- }
-
- // Registered whether we're bringing this package out of a stopped state
- r.mWasReceiverAppStopped =
- (info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0;
- // Not running -- get it started, to be executed when the app comes up.
- if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,
- "Need to start app ["
- + mQueueName + "] " + targetProcess + " for broadcast " + r);
- r.curApp = mService.startProcessLocked(targetProcess,
- info.activityInfo.applicationInfo, true,
- r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
- new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST, r.curComponent,
- r.intent.getAction(), r.getHostingRecordTriggerType()),
- isActivityCapable ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
- (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
- r.curAppLastProcessState = ActivityManager.PROCESS_STATE_NONEXISTENT;
- if (r.curApp == null) {
- // Ah, this recipient is unavailable. Finish it if necessary,
- // and mark the broadcast record as ready for the next.
- Slog.w(TAG, "Unable to launch app "
- + info.activityInfo.applicationInfo.packageName + "/"
- + receiverUid + " for broadcast "
- + r.intent + ": process is bad");
- logBroadcastReceiverDiscardLocked(r);
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
- r.state = BroadcastRecord.IDLE;
- return;
- }
-
- maybeAddBackgroundStartPrivileges(r.curApp, r);
- mPendingBroadcast = r;
- mPendingBroadcastRecvIndex = recIdx;
- }
-
- @Nullable
- private String getTargetPackage(BroadcastRecord r) {
- if (r.intent == null) {
- return null;
- }
- if (r.intent.getPackage() != null) {
- return r.intent.getPackage();
- } else if (r.intent.getComponent() != null) {
- return r.intent.getComponent().getPackageName();
- }
- return null;
- }
-
- static void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
- // Only log after last receiver.
- // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
- // last BroadcastRecord of the split broadcast which has non-null resultTo.
- final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
- if (r.nextReceiver < numReceivers) {
- return;
- }
- final String action = r.intent.getAction();
- int event = 0;
- if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
- } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
- }
- if (event != 0) {
- final int dispatchLatency = (int)(r.dispatchTime - r.enqueueTime);
- final int completeLatency = (int)
- (SystemClock.uptimeMillis() - r.enqueueTime);
- final int dispatchRealLatency = (int)(r.dispatchRealTime - r.enqueueRealTime);
- final int completeRealLatency = (int)
- (SystemClock.elapsedRealtime() - r.enqueueRealTime);
- int userType = FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
- // This method is called very infrequently, no performance issue we call
- // LocalServices.getService() here.
- final UserManagerInternal umInternal = LocalServices.getService(
- UserManagerInternal.class);
- final UserInfo userInfo =
- (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
- if (userInfo != null) {
- userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
- }
- Slog.i(TAG_BROADCAST,
- "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
- + action
- + " dispatchLatency:" + dispatchLatency
- + " completeLatency:" + completeLatency
- + " dispatchRealLatency:" + dispatchRealLatency
- + " completeRealLatency:" + completeRealLatency
- + " receiversSize:" + numReceivers
- + " userId:" + r.userId
- + " userType:" + (userInfo != null? userInfo.userType : null));
- FrameworkStatsLog.write(
- BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
- event,
- dispatchLatency,
- completeLatency,
- dispatchRealLatency,
- completeRealLatency,
- r.userId,
- userType);
- }
- }
-
- private void maybeReportBroadcastDispatchedEventLocked(BroadcastRecord r, int targetUid) {
- if (r.options == null || r.options.getIdForResponseEvent() <= 0) {
- return;
- }
- final String targetPackage = getTargetPackage(r);
- // Ignore non-explicit broadcasts
- if (targetPackage == null) {
- return;
- }
- mService.mUsageStatsService.reportBroadcastDispatched(
- r.callingUid, targetPackage, UserHandle.of(r.userId),
- r.options.getIdForResponseEvent(), SystemClock.elapsedRealtime(),
- mService.getUidStateLocked(targetUid));
- }
-
- private void maybeAddBackgroundStartPrivileges(ProcessRecord proc, BroadcastRecord r) {
- if (r == null || proc == null || !r.mBackgroundStartPrivileges.allowsAny()) {
- return;
- }
- String msgToken = (proc.toShortString() + r.toString()).intern();
- // first, if there exists a past scheduled request to remove this token, drop
- // that request - we don't want the token to be swept from under our feet...
- mHandler.removeCallbacksAndMessages(msgToken);
- // ...then add the token
- proc.addOrUpdateBackgroundStartPrivileges(r, r.mBackgroundStartPrivileges);
- }
-
- final void setBroadcastTimeoutLocked(long timeoutTime) {
- if (! mPendingBroadcastTimeoutMessage) {
- Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
- mHandler.sendMessageAtTime(msg, timeoutTime);
- mPendingBroadcastTimeoutMessage = true;
- }
- }
-
- final void cancelBroadcastTimeoutLocked() {
- if (mPendingBroadcastTimeoutMessage) {
- mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
- mPendingBroadcastTimeoutMessage = false;
- }
- }
-
- final void broadcastTimeoutLocked(boolean fromMsg) {
- if (fromMsg) {
- mPendingBroadcastTimeoutMessage = false;
- }
-
- if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
- return;
- }
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastTimeoutLocked()");
- try {
- long now = SystemClock.uptimeMillis();
- BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
- if (fromMsg) {
- if (!mService.mProcessesReady) {
- // Only process broadcast timeouts if the system is ready; some early
- // broadcasts do heavy work setting up system facilities
- return;
- }
-
- // If the broadcast is generally exempt from timeout tracking, we're done
- if (r.timeoutExempt) {
- if (DEBUG_BROADCAST) {
- Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
- + r.intent.getAction());
- }
- return;
- }
- long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
- if (timeoutTime > now) {
- // We can observe premature timeouts because we do not cancel and reset the
- // broadcast timeout message after each receiver finishes. Instead, we set up
- // an initial timeout then kick it down the road a little further as needed
- // when it expires.
- if (DEBUG_BROADCAST) {
- Slog.v(TAG_BROADCAST,
- "Premature timeout ["
- + mQueueName + "] @ " + now
- + ": resetting BROADCAST_TIMEOUT_MSG for "
- + timeoutTime);
- }
- setBroadcastTimeoutLocked(timeoutTime);
- return;
- }
- }
-
- if (r.state == BroadcastRecord.WAITING_SERVICES) {
- // In this case the broadcast had already finished, but we had decided to wait
- // for started services to finish as well before going on. So if we have actually
- // waited long enough time timeout the broadcast, let's give up on the whole thing
- // and just move on to the next.
- Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null
- ? r.curComponent.flattenToShortString() : "(null)"));
- r.curComponent = null;
- r.state = BroadcastRecord.IDLE;
- processNextBroadcastLocked(false, false);
- return;
- }
-
- // If the receiver app is being debugged we quietly ignore unresponsiveness, just
- // tidying up and moving on to the next broadcast without crashing or ANRing this
- // app just because it's stopped at a breakpoint.
- final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
-
- long timeoutDurationMs = now - r.receiverTime;
- Slog.w(TAG, "Timeout of broadcast " + r + " - curFilter=" + r.curFilter
- + " curReceiver=" + r.curReceiver + ", started " + timeoutDurationMs
- + "ms ago");
- r.receiverTime = now;
- if (!debugging) {
- r.anrCount++;
- }
-
- ProcessRecord app = null;
- Object curReceiver;
- if (r.nextReceiver > 0) {
- curReceiver = r.receivers.get(r.nextReceiver - 1);
- r.delivery[r.nextReceiver - 1] = BroadcastRecord.DELIVERY_TIMEOUT;
- } else {
- curReceiver = r.curReceiver;
- }
- Slog.w(TAG, "Receiver during timeout of " + r + " : " + curReceiver);
- logBroadcastReceiverDiscardLocked(r);
- TimeoutRecord timeoutRecord = TimeoutRecord.forBroadcastReceiver(r.intent,
- timeoutDurationMs);
- if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- if (bf.receiverList.pid != 0
- && bf.receiverList.pid != ActivityManagerService.MY_PID) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockStarted();
- synchronized (mService.mPidsSelfLocked) {
- timeoutRecord.mLatencyTracker.waitingOnPidLockEnded();
- app = mService.mPidsSelfLocked.get(
- bf.receiverList.pid);
- }
- }
- } else {
- app = r.curApp;
- }
-
- if (mPendingBroadcast == r) {
- mPendingBroadcast = null;
- }
-
- // Move on to the next receiver.
- finishReceiverLocked(r, r.resultCode, r.resultData,
- r.resultExtras, r.resultAbort, false);
- scheduleBroadcastsLocked();
-
- // The ANR should only be triggered if we have a process record (app is non-null)
- if (!debugging && app != null) {
- mService.appNotResponding(app, timeoutRecord);
- }
-
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
-
- }
-
- private final void addBroadcastToHistoryLocked(BroadcastRecord original) {
- if (original.callingUid < 0) {
- // This was from a registerReceiver() call; ignore it.
- return;
- }
- original.finishTime = SystemClock.uptimeMillis();
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
- createBroadcastTraceTitle(original, BroadcastRecord.DELIVERY_DELIVERED),
- System.identityHashCode(original));
- }
-
- mService.notifyBroadcastFinishedLocked(original);
- mHistory.addBroadcastToHistoryLocked(original);
- }
-
- public boolean cleanupDisabledPackageReceiversLocked(
- String packageName, Set<String> filterByClasses, int userId) {
- boolean didSomething = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- didSomething |= mParallelBroadcasts.get(i).cleanupDisabledPackageReceiversLocked(
- packageName, filterByClasses, userId, true);
- }
-
- didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName,
- filterByClasses, userId, true);
-
- return didSomething;
- }
-
- final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
- final int logIndex = r.nextReceiver - 1;
- if (logIndex >= 0 && logIndex < r.receivers.size()) {
- Object curReceiver = r.receivers.get(logIndex);
- if (curReceiver instanceof BroadcastFilter) {
- BroadcastFilter bf = (BroadcastFilter) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
- bf.owningUserId, System.identityHashCode(r),
- r.intent.getAction(), logIndex, System.identityHashCode(bf));
- } else {
- ResolveInfo ri = (ResolveInfo) curReceiver;
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
- System.identityHashCode(r), r.intent.getAction(), logIndex, ri.toString());
- }
- } else {
- if (logIndex < 0) Slog.w(TAG,
- "Discarding broadcast before first receiver is invoked: " + r);
- EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
- -1, System.identityHashCode(r),
- r.intent.getAction(),
- r.nextReceiver,
- "NONE");
- }
- }
-
- private String createBroadcastTraceTitle(BroadcastRecord record, int state) {
- return formatSimple("Broadcast %s from %s (%s) %s",
- state == BroadcastRecord.DELIVERY_PENDING ? "in queue" : "dispatched",
- record.callerPackage == null ? "" : record.callerPackage,
- record.callerApp == null ? "process unknown" : record.callerApp.toShortString(),
- record.intent == null ? "" : record.intent.getAction());
- }
-
- public boolean isIdleLocked() {
- return mParallelBroadcasts.isEmpty() && mDispatcher.isIdle()
- && (mPendingBroadcast == null);
- }
-
- public boolean isBeyondBarrierLocked(long barrierTime) {
- // If nothing active, we're beyond barrier
- if (isIdleLocked()) return true;
-
- // Check if parallel broadcasts are beyond barrier
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (mParallelBroadcasts.get(i).enqueueTime <= barrierTime) {
- return false;
- }
- }
-
- // Check if pending broadcast is beyond barrier
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && pending.enqueueTime <= barrierTime) {
- return false;
- }
-
- return mDispatcher.isBeyondBarrier(barrierTime);
- }
-
- public boolean isDispatchedLocked(Intent intent) {
- if (isIdleLocked()) return true;
-
- for (int i = 0; i < mParallelBroadcasts.size(); i++) {
- if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
- return false;
- }
- }
-
- final BroadcastRecord pending = getPendingBroadcastLocked();
- if ((pending != null) && intent.filterEquals(pending.intent)) {
- return false;
- }
-
- return mDispatcher.isDispatched(intent);
- }
-
- public void waitForIdle(PrintWriter pw) {
- waitFor(() -> isIdleLocked(), pw, "idle");
- }
-
- public void waitForBarrier(PrintWriter pw) {
- final long barrierTime = SystemClock.uptimeMillis();
- waitFor(() -> isBeyondBarrierLocked(barrierTime), pw, "barrier");
- }
-
- public void waitForDispatched(Intent intent, PrintWriter pw) {
- waitFor(() -> isDispatchedLocked(intent), pw, "dispatch");
- }
-
- private void waitFor(BooleanSupplier condition, PrintWriter pw, String conditionName) {
- long lastPrint = 0;
- while (true) {
- synchronized (mService) {
- if (condition.getAsBoolean()) {
- final String msg = "Queue [" + mQueueName + "] reached " + conditionName
- + " condition";
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- return;
- }
- }
-
- // Print at most every second
- final long now = SystemClock.uptimeMillis();
- if (now >= lastPrint + 1000) {
- lastPrint = now;
- final String msg = "Queue [" + mQueueName + "] waiting for " + conditionName
- + " condition; state is " + describeStateLocked();
- Slog.v(TAG, msg);
- if (pw != null) {
- pw.println(msg);
- pw.flush();
- }
- }
-
- // Push through any deferrals to try meeting our condition
- cancelDeferrals();
- SystemClock.sleep(100);
- }
- }
-
- // Used by wait-for-broadcast-idle : fast-forward all current deferrals to
- // be immediately deliverable.
- public void cancelDeferrals() {
- synchronized (mService) {
- mDispatcher.cancelDeferralsLocked();
- scheduleBroadcastsLocked();
- }
- }
-
- public String describeStateLocked() {
- return mParallelBroadcasts.size() + " parallel; "
- + mDispatcher.describeStateLocked();
- }
-
- @NeverCompile
- public void dumpDebug(ProtoOutputStream proto, long fieldId) {
- long token = proto.start(fieldId);
- proto.write(BroadcastQueueProto.QUEUE_NAME, mQueueName);
- int N;
- N = mParallelBroadcasts.size();
- for (int i = N - 1; i >= 0; i--) {
- mParallelBroadcasts.get(i).dumpDebug(proto, BroadcastQueueProto.PARALLEL_BROADCASTS);
- }
- mDispatcher.dumpDebug(proto, BroadcastQueueProto.ORDERED_BROADCASTS);
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dumpDebug(proto, BroadcastQueueProto.PENDING_BROADCAST);
- }
- mHistory.dumpDebug(proto);
- proto.end(token);
- }
-
- @NeverCompile
- public boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
- int opti, boolean dumpConstants, boolean dumpHistory, boolean dumpAll,
- String dumpPackage, boolean needSep) {
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty()
- || mPendingBroadcast != null) {
- boolean printed = false;
- for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) {
- BroadcastRecord br = mParallelBroadcasts.get(i);
- if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
- continue;
- }
- if (!printed) {
- if (needSep) {
- pw.println();
- }
- needSep = true;
- printed = true;
- pw.println(" Active broadcasts [" + mQueueName + "]:");
- }
- pw.println(" Active Broadcast " + mQueueName + " #" + i + ":");
- br.dump(pw, " ", sdf);
- }
-
- mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf);
-
- if (dumpPackage == null || (mPendingBroadcast != null
- && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
- pw.println();
- pw.println(" Pending broadcast [" + mQueueName + "]:");
- if (mPendingBroadcast != null) {
- mPendingBroadcast.dump(pw, " ", sdf);
- } else {
- pw.println(" (null)");
- }
- needSep = true;
- }
- }
- if (dumpConstants) {
- mConstants.dump(new IndentingPrintWriter(pw));
- }
- if (dumpHistory) {
- needSep = mHistory.dumpLocked(pw, dumpPackage, mQueueName, sdf, dumpAll, needSep);
- }
- return needSep;
- }
-}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 98263df..4422608 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -20,6 +20,9 @@
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+import static com.android.internal.util.FrameworkStatsLog.BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_UNKNOWN;
@@ -59,6 +62,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.Handler;
@@ -85,9 +89,12 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.am.BroadcastProcessQueue.BroadcastConsumer;
import com.android.server.am.BroadcastProcessQueue.BroadcastPredicate;
import com.android.server.am.BroadcastRecord.DeliveryState;
+import com.android.server.pm.UserJourneyLogger;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.AnrTimer;
import dalvik.annotation.optimization.NeverCompile;
@@ -2120,7 +2127,7 @@
r.nextReceiver = r.receivers.size();
mHistory.onBroadcastFinishedLocked(r);
- BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
+ logBootCompletedBroadcastCompletionLatencyIfPossible(r);
if (r.intent.getComponent() == null && r.intent.getPackage() == null
&& (r.intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
@@ -2216,6 +2223,59 @@
return null;
}
+ private void logBootCompletedBroadcastCompletionLatencyIfPossible(BroadcastRecord r) {
+ // Only log after last receiver.
+ // In case of split BOOT_COMPLETED broadcast, make sure only call this method on the
+ // last BroadcastRecord of the split broadcast which has non-null resultTo.
+ final int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+ if (r.nextReceiver < numReceivers) {
+ return;
+ }
+ final String action = r.intent.getAction();
+ int event = 0;
+ if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__LOCKED_BOOT_COMPLETED;
+ } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ event = BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED__EVENT__BOOT_COMPLETED;
+ }
+ if (event != 0) {
+ final int dispatchLatency = (int) (r.dispatchTime - r.enqueueTime);
+ final int completeLatency = (int) (SystemClock.uptimeMillis() - r.enqueueTime);
+ final int dispatchRealLatency = (int) (r.dispatchRealTime - r.enqueueRealTime);
+ final int completeRealLatency = (int)
+ (SystemClock.elapsedRealtime() - r.enqueueRealTime);
+ int userType =
+ FrameworkStatsLog.USER_LIFECYCLE_JOURNEY_REPORTED__USER_TYPE__TYPE_UNKNOWN;
+ // This method is called very infrequently, no performance issue we call
+ // LocalServices.getService() here.
+ final UserManagerInternal umInternal = LocalServices.getService(
+ UserManagerInternal.class);
+ final UserInfo userInfo =
+ (umInternal != null) ? umInternal.getUserInfo(r.userId) : null;
+ if (userInfo != null) {
+ userType = UserJourneyLogger.getUserTypeForStatsd(userInfo.userType);
+ }
+ Slog.i(TAG, "BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED action:"
+ + action
+ + " dispatchLatency:" + dispatchLatency
+ + " completeLatency:" + completeLatency
+ + " dispatchRealLatency:" + dispatchRealLatency
+ + " completeRealLatency:" + completeRealLatency
+ + " receiversSize:" + numReceivers
+ + " userId:" + r.userId
+ + " userType:" + (userInfo != null ? userInfo.userType : null));
+ FrameworkStatsLog.write(
+ BOOT_COMPLETED_BROADCAST_COMPLETION_LATENCY_REPORTED,
+ event,
+ dispatchLatency,
+ completeLatency,
+ dispatchRealLatency,
+ completeRealLatency,
+ r.userId,
+ userType);
+ }
+ }
+
@Override
@NeverCompile
public void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index 4ef31bf..f2b9b25 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.Nullable;
import android.app.IServiceConnection;
@@ -37,7 +38,7 @@
/**
* Description of a single binding to a service.
*/
-final class ConnectionRecord {
+final class ConnectionRecord implements OomAdjusterModernImpl.Connection{
final AppBindRecord binding; // The application/service binding.
final ActivityServiceConnectionsHolder<ConnectionRecord> activity; // If non-null, the owning activity.
final IServiceConnection conn; // The client connection.
@@ -127,6 +128,14 @@
aliasComponent = _aliasComponent;
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeServiceHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public long getFlags() {
return flags;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 825b938..3988277 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -18,6 +18,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROVIDER;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import android.annotation.UserIdInt;
import android.os.Binder;
@@ -32,7 +33,8 @@
/**
* Represents a link between a content provider and client.
*/
-public final class ContentProviderConnection extends Binder {
+public final class ContentProviderConnection extends Binder implements
+ OomAdjusterModernImpl.Connection {
public final ContentProviderRecord provider;
public final ProcessRecord client;
public final String clientPackage;
@@ -72,6 +74,14 @@
createTime = SystemClock.elapsedRealtime();
}
+ @Override
+ public void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host,
+ ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
+ int oomAdjReason, int cachedAdj) {
+ oomAdjuster.computeProviderHostOomAdjLSP(this, host, client, now, topApp, doingAll, false,
+ false, oomAdjReason, UNKNOWN_ADJ, false, false);
+ }
+
public void startAssociationIfNeeded() {
// If we don't already have an active association, create one... but only if this
// is an association between two different processes.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 31328ae..cd6964e 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -587,7 +587,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- private void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
// Clear any pending ones because we are doing a full update now.
mPendingProcessSet.clear();
@@ -913,7 +913,7 @@
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -941,7 +941,7 @@
* must have called {@link collectReachableProcessesLocked} on it.
*/
@GuardedBy({"mService", "mProcLock"})
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
+ private void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
boolean startProfiling) {
final boolean fullUpdate = processes == null;
@@ -1775,12 +1775,11 @@
state.setAdjSeq(mAdjSeq);
state.setCurrentSchedulingGroup(SCHED_GROUP_BACKGROUND);
state.setCurProcState(PROCESS_STATE_CACHED_EMPTY);
+ state.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
state.setCurAdj(CACHED_APP_MAX_ADJ);
state.setCurRawAdj(CACHED_APP_MAX_ADJ);
state.setCompletedAdjSeq(state.getAdjSeq());
state.setCurCapability(PROCESS_CAPABILITY_NONE);
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
return false;
}
@@ -1843,8 +1842,6 @@
state.setCurRawProcState(state.getCurProcState());
state.setCurAdj(state.getMaxAdj());
state.setCompletedAdjSeq(state.getAdjSeq());
- onProcessStateChanged(app, prevProcState);
- onProcessOomAdjChanged(app, prevAppAdj);
// if curAdj is less than prevAppAdj, then this process was promoted
return state.getCurAdj() < prevAppAdj || state.getCurProcState() < prevProcState;
}
@@ -2561,7 +2558,7 @@
}
@GuardedBy({"mService", "mProcLock"})
- protected boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
+ public boolean computeServiceHostOomAdjLSP(ConnectionRecord cr, ProcessRecord app,
ProcessRecord client, long now, ProcessRecord topApp, boolean doingAll,
boolean cycleReEval, boolean computeClients, int oomAdjReason, int cachedAdj,
boolean couldRecurse, boolean dryRun) {
@@ -2991,7 +2988,11 @@
return updated;
}
- protected boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
+ /**
+ * Computes the impact on {@code app} the provider connections from {@code client} has.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ public boolean computeProviderHostOomAdjLSP(ContentProviderConnection conn,
ProcessRecord app, ProcessRecord client, long now, ProcessRecord topApp,
boolean doingAll, boolean cycleReEval, boolean computeClients, int oomAdjReason,
int cachedAdj, boolean couldRecurse, boolean dryRun) {
@@ -3572,7 +3573,7 @@
int initialCapability = PROCESS_CAPABILITY_NONE;
boolean initialCached = true;
final ProcessStateRecord state = app.mState;
- final int prevProcState = state.getCurRawProcState();
+ final int prevProcState = state.getCurProcState();
final int prevAdj = state.getCurRawAdj();
// If the process has been marked as foreground, it is starting as the top app (with
// Zygote#START_AS_TOP_APP_ARG), so boost the thread priority of its default UI thread.
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index 1bf779a..dd75bc0 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -38,7 +38,6 @@
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
@@ -67,8 +66,10 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal.OomAdjReason;
import android.content.pm.ServiceInfo;
+import android.os.IBinder;
import android.os.SystemClock;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -80,6 +81,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -275,6 +277,7 @@
mProcessRecordNodes[i] = new LinkedProcessRecordList(type);
}
mLastNode = new ProcessRecordNode[size];
+ resetLastNodes();
}
int size() {
@@ -285,7 +288,7 @@
void reset() {
for (int i = 0; i < mProcessRecordNodes.length; i++) {
mProcessRecordNodes[i].reset();
- mLastNode[i] = null;
+ setLastNodeToHead(i);
}
}
@@ -509,26 +512,118 @@
}
/**
- * A helper consumer for collecting processes that have not been reached yet. To avoid object
- * allocations every OomAdjuster update, the results will be stored in
- * {@link UnreachedProcessCollector#processList}. The process list reader is responsible
- * for setting it before usage, as well as, clearing the reachable state of each process in the
- * list.
+ * A {@link Connection} represents any connection between two processes that can cause a
+ * change in importance in the host process based on the client process and connection state.
*/
- private static class UnreachedProcessCollector implements Consumer<ProcessRecord> {
- public ArrayList<ProcessRecord> processList = null;
+ public interface Connection {
+ /**
+ * Compute the impact this connection has on the host's importance values.
+ */
+ void computeHostOomAdjLSP(OomAdjuster oomAdjuster, ProcessRecord host, ProcessRecord client,
+ long now, ProcessRecord topApp, boolean doingAll, int oomAdjReason, int cachedAdj);
+ }
+
+ /**
+ * A helper consumer for marking and collecting reachable processes.
+ */
+ private static class ReachableCollectingConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ ArrayList<ProcessRecord> mReachables = null;
+
+ public void init(ArrayList<ProcessRecord> reachables) {
+ mReachables = reachables;
+ }
+
@Override
- public void accept(ProcessRecord process) {
- if (process.mState.isReachable()) {
+ public void accept(Connection unused, ProcessRecord host) {
+ if (host.mState.isReachable()) {
return;
}
- process.mState.setReachable(true);
- processList.add(process);
+ host.mState.setReachable(true);
+ mReachables.add(host);
}
}
- private final UnreachedProcessCollector mUnreachedProcessCollector =
- new UnreachedProcessCollector();
+ private final ReachableCollectingConsumer mReachableCollectingConsumer =
+ new ReachableCollectingConsumer();
+
+ /**
+ * A helper consumer for computing the importance of a connection from a client.
+ * Connections for clients marked reachable will be ignored.
+ */
+ private class ComputeConnectionIgnoringReachableClientsConsumer implements
+ BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord client) {
+ final ProcessRecord host = args.mApp;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+
+ if (client.mState.isReachable()) return;
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp, false,
+ oomAdjReason, UNKNOWN_ADJ);
+ }
+ }
+
+ private final ComputeConnectionIgnoringReachableClientsConsumer
+ mComputeConnectionIgnoringReachableClientsConsumer =
+ new ComputeConnectionIgnoringReachableClientsConsumer();
+
+ /**
+ * A helper consumer for computing host process importance from a connection from a client app.
+ */
+ private class ComputeHostConsumer implements BiConsumer<Connection, ProcessRecord> {
+ public OomAdjusterArgs args = null;
+
+ @Override
+ public void accept(Connection conn, ProcessRecord host) {
+ final ProcessRecord client = args.mApp;
+ final int cachedAdj = args.mCachedAdj;
+ final ProcessRecord topApp = args.mTopApp;
+ final long now = args.mNow;
+ final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
+ final boolean fullUpdate = args.mFullUpdate;
+
+ final int prevProcState = host.mState.getCurProcState();
+ final int prevAdj = host.mState.getCurRawAdj();
+
+ conn.computeHostOomAdjLSP(OomAdjusterModernImpl.this, host, client, now, topApp,
+ fullUpdate, oomAdjReason, cachedAdj);
+
+ updateProcStateSlotIfNecessary(host, prevProcState);
+ updateAdjSlotIfNecessary(host, prevAdj);
+ }
+ }
+ private final ComputeHostConsumer mComputeHostConsumer = new ComputeHostConsumer();
+
+ /**
+ * A helper consumer for computing all connections from an app.
+ */
+ private class ComputeConnectionsConsumer implements Consumer<OomAdjusterArgs> {
+ @Override
+ public void accept(OomAdjusterArgs args) {
+ final ProcessRecord app = args.mApp;
+ final ActiveUids uids = args.mUids;
+
+ // This process was updated in some way, mark that it was last calculated this sequence.
+ app.mState.setCompletedAdjSeq(mAdjSeq);
+ if (uids != null) {
+ final UidRecord uidRec = app.getUidRecord();
+
+ if (uidRec != null) {
+ uids.put(uidRec.getUid(), uidRec);
+ }
+ }
+ mComputeHostConsumer.args = args;
+ forEachConnectionLSP(app, mComputeHostConsumer);
+ }
+ }
+ private final ComputeConnectionsConsumer mComputeConnectionsConsumer =
+ new ComputeConnectionsConsumer();
OomAdjusterModernImpl(ActivityManagerService service, ProcessList processList,
ActiveUids activeUids) {
@@ -617,6 +712,12 @@
}
}
+ private void updateAdjSlot(ProcessRecord app, int prevRawAdj) {
+ final int slot = adjToSlot(app.mState.getCurRawAdj());
+ final int prevSlot = adjToSlot(prevRawAdj);
+ mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot);
+ }
+
private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) {
if (app.mState.getCurProcState() != prevProcState) {
final int slot = processStateToSlot(app.mState.getCurProcState());
@@ -627,359 +728,247 @@
}
}
+ private void updateProcStateSlot(ProcessRecord app, int prevProcState) {
+ final int slot = processStateToSlot(app.mState.getCurProcState());
+ final int prevSlot = processStateToSlot(prevProcState);
+ mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot);
+ }
+
@Override
- protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ protected void performUpdateOomAdjLSP(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
+ // Clear any pending ones because we are doing a full update now.
+ mPendingProcessSet.clear();
+ mService.mAppProfiler.mHasPreviousProcess = mService.mAppProfiler.mHasHomeProcess = false;
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
mService.mOomAdjProfiler.oomAdjStarted();
- mAdjSeq++;
- final ProcessStateRecord state = app.mState;
- final int oldAdj = state.getCurRawAdj();
- final int cachedAdj = oldAdj >= CACHED_APP_MIN_ADJ
- ? oldAdj : UNKNOWN_ADJ;
-
- final ActiveUids uids = mTmpUidRecords;
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- final ArrayList<ProcessRecord> reachableProcesses = mTmpProcessList;
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
-
- uids.clear();
- targetProcesses.clear();
- targetProcesses.add(app);
- reachableProcesses.clear();
-
- // Find out all reachable processes from this app.
- collectReachableProcessesLocked(targetProcesses, reachableProcesses, uids);
-
- // Copy all of the reachable processes into the target process set.
- targetProcesses.addAll(reachableProcesses);
- reachableProcesses.clear();
-
- final boolean result = performNewUpdateOomAdjLSP(oomAdjReason,
- topApp, targetProcesses, uids, false, now, cachedAdj);
-
- reachableProcesses.addAll(targetProcesses);
- assignCachedAdjIfNecessary(reachableProcesses);
- for (int i = uids.size() - 1; i >= 0; i--) {
- final UidRecord uidRec = uids.valueAt(i);
- uidRec.forEachProcess(this::updateAppUidRecIfNecessaryLSP);
- }
- updateUidsLSP(uids, nowElapsed);
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- applyOomAdjLSP(targetProcesses.valueAt(i), false, now, nowElapsed, oomAdjReason);
- }
- targetProcesses.clear();
- reachableProcesses.clear();
+ fullUpdateLSP(oomAdjReason);
mService.mOomAdjProfiler.oomAdjEnded();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- return result;
}
@GuardedBy({"mService", "mProcLock"})
@Override
- protected void updateOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
- boolean startProfiling) {
- final boolean fullUpdate = processes == null;
- final ArrayList<ProcessRecord> activeProcesses = fullUpdate
- ? mProcessList.getLruProcessesLOSP() : processes;
- ActiveUids activeUids = uids;
- if (activeUids == null) {
- final int numUids = mActiveUids.size();
- activeUids = mTmpUidRecords;
- activeUids.clear();
- for (int i = 0; i < numUids; i++) {
- UidRecord uidRec = mActiveUids.valueAt(i);
- activeUids.put(uidRec.getUid(), uidRec);
- }
- }
-
- if (startProfiling) {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
- mService.mOomAdjProfiler.oomAdjStarted();
- }
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- final int numProc = activeProcesses.size();
-
- mAdjSeq++;
- if (fullUpdate) {
- mNewNumServiceProcs = 0;
- mNewNumAServiceProcs = 0;
- }
-
- final ArraySet<ProcessRecord> targetProcesses = mTmpProcessSet;
- targetProcesses.clear();
- if (!fullUpdate) {
- targetProcesses.addAll(activeProcesses);
- }
-
- performNewUpdateOomAdjLSP(oomAdjReason, topApp, targetProcesses, activeUids,
- fullUpdate, now, UNKNOWN_ADJ);
-
- // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
- postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
- targetProcesses.clear();
-
- if (startProfiling) {
- mService.mOomAdjProfiler.oomAdjEnded();
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- }
- return;
- }
-
- /**
- * Perform the oom adj update on the given {@code targetProcesses}.
- *
- * <p>Note: The expectation to the given {@code targetProcesses} is, the caller
- * must have called {@link collectReachableProcessesLocked} on it.
- */
- private boolean performNewUpdateOomAdjLSP(@OomAdjReason int oomAdjReason,
- ProcessRecord topApp, ArraySet<ProcessRecord> targetProcesses, ActiveUids uids,
- boolean fullUpdate, long now, int cachedAdj) {
-
- final ArrayList<ProcessRecord> clientProcesses = mTmpProcessList2;
- clientProcesses.clear();
-
- // We'll need to collect the upstream processes of the target apps here, because those
- // processes would potentially impact the procstate/adj via bindings.
- if (!fullUpdate) {
- collectExcludedClientProcessesLocked(targetProcesses, clientProcesses);
-
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
- }
- } else {
- final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
- for (int i = 0, size = lru.size(); i < size; i++) {
- final ProcessRecord app = lru.get(i);
- app.mState.resetCachedInfo();
- final UidRecord uidRec = app.getUidRecord();
- if (uidRec != null) {
- if (DEBUG_UID_OBSERVERS) {
- Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
- }
- uidRec.reset();
- }
- }
- }
-
- updateNewOomAdjInnerLSP(oomAdjReason, topApp, targetProcesses, clientProcesses, uids,
- cachedAdj, now, fullUpdate);
-
- clientProcesses.clear();
-
+ protected boolean performUpdateOomAdjLSP(ProcessRecord app, @OomAdjReason int oomAdjReason) {
+ mPendingProcessSet.add(app);
+ performUpdateOomAdjPendingTargetsLocked(oomAdjReason);
return true;
}
- /**
- * Collect the client processes from the given {@code apps}, the result will be returned in the
- * given {@code clientProcesses}, which will <em>NOT</em> include the processes from the given
- * {@code apps}.
- */
@GuardedBy("mService")
- private void collectExcludedClientProcessesLocked(ArraySet<ProcessRecord> apps,
- ArrayList<ProcessRecord> clientProcesses) {
- // Mark all of the provided apps as reachable to avoid including them in the client list.
- final int appsSize = apps.size();
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(true);
- }
+ @Override
+ protected void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
+ mService.mOomAdjProfiler.oomAdjStarted();
- clientProcesses.clear();
- mUnreachedProcessCollector.processList = clientProcesses;
- for (int i = 0; i < appsSize; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.forEachClient(mUnreachedProcessCollector);
+ synchronized (mProcLock) {
+ partialUpdateLSP(oomAdjReason, mPendingProcessSet);
}
- mUnreachedProcessCollector.processList = null;
+ mPendingProcessSet.clear();
- // Reset the temporary bits.
- for (int i = clientProcesses.size() - 1; i >= 0; i--) {
- clientProcesses.get(i).mState.setReachable(false);
- }
- for (int i = 0, size = apps.size(); i < size; i++) {
- final ProcessRecord app = apps.valueAt(i);
- app.mState.setReachable(false);
- }
+ mService.mOomAdjProfiler.oomAdjEnded();
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
+ /**
+ * Perform a full update on the entire process list.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void updateNewOomAdjInnerLSP(@OomAdjReason int oomAdjReason, final ProcessRecord topApp,
- ArraySet<ProcessRecord> targetProcesses, ArrayList<ProcessRecord> clientProcesses,
- ActiveUids uids, int cachedAdj, long now, boolean fullUpdate) {
- mTmpOomAdjusterArgs.update(topApp, now, cachedAdj, oomAdjReason, uids, fullUpdate);
-
- mProcessRecordProcStateNodes.resetLastNodes();
- mProcessRecordAdjNodes.resetLastNodes();
-
- final int procStateTarget = mProcessRecordProcStateNodes.size() - 1;
- final int adjTarget = mProcessRecordAdjNodes.size() - 1;
+ private void fullUpdateLSP(@OomAdjReason int oomAdjReason) {
+ final ProcessRecord topApp = mService.getTopApp();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
mAdjSeq++;
- // All apps to be updated will be moved to the lowest slot.
- if (fullUpdate) {
- // Move all the process record node to the lowest slot, we'll do recomputation on all of
- // them. Use the processes from the lru list, because the scanning order matters here.
- final ArrayList<ProcessRecord> lruList = mProcessList.getLruProcessesLOSP();
- for (int i = procStateTarget; i >= 0; i--) {
- mProcessRecordProcStateNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordProcStateNodes.setLastNodeToHead(i);
+
+ mNewNumServiceProcs = 0;
+ mNewNumAServiceProcs = 0;
+
+ // Clear the priority queues.
+ mProcessRecordProcStateNodes.reset();
+ mProcessRecordAdjNodes.reset();
+
+ final ArrayList<ProcessRecord> lru = mProcessList.getLruProcessesLOSP();
+ for (int i = lru.size() - 1; i >= 0; i--) {
+ final ProcessRecord app = lru.get(i);
+ final int prevProcState = app.mState.getCurProcState();
+ final int prevAdj = app.mState.getCurRawAdj();
+ app.mState.resetCachedInfo();
+ final UidRecord uidRec = app.getUidRecord();
+ if (uidRec != null) {
+ if (DEBUG_UID_OBSERVERS) {
+ Slog.i(TAG_UID_OBSERVERS, "Starting update of " + uidRec);
+ }
+ uidRec.reset();
}
- // enqueue the targets in the reverse order of the lru list.
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordProcStateNodes.append(lruList.get(i), procStateTarget);
- }
- // Do the same to the adj nodes.
- for (int i = adjTarget; i >= 0; i--) {
- mProcessRecordAdjNodes.reset(i);
- // Force the last node to the head since we'll recompute all of them.
- mProcessRecordAdjNodes.setLastNodeToHead(i);
- }
- for (int i = lruList.size() - 1; i >= 0; i--) {
- mProcessRecordAdjNodes.append(lruList.get(i), adjTarget);
- }
- } else {
- // Move the target processes to the lowest slot.
- for (int i = 0, size = targetProcesses.size(); i < size; i++) {
- final ProcessRecord app = targetProcesses.valueAt(i);
- final int procStateSlot = processStateToSlot(app.mState.getCurProcState());
- final int adjSlot = adjToSlot(app.mState.getCurRawAdj());
- mProcessRecordProcStateNodes.moveAppTo(app, procStateSlot, procStateTarget);
- mProcessRecordAdjNodes.moveAppTo(app, adjSlot, adjTarget);
- }
- // Move the "lastNode" to head to make sure we scan all nodes in this slot.
- mProcessRecordProcStateNodes.setLastNodeToHead(procStateTarget);
- mProcessRecordAdjNodes.setLastNodeToHead(adjTarget);
+
+ // Compute initial values, the procState and adj priority queues will be populated here.
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason,
+ false);
+ updateProcStateSlot(app, prevProcState);
+ updateAdjSlot(app, prevAdj);
}
- // All apps to be updated have been moved to the lowest slot.
- // Do an initial pass of the computation.
- mProcessRecordProcStateNodes.forEachNewNode(mProcessRecordProcStateNodes.size() - 1,
- this::computeInitialOomAdjLSP);
-
- if (!fullUpdate) {
- // We didn't update the client processes with the computeInitialOomAdjLSP
- // because they don't need to do so. But they'll be playing vital roles in
- // computing the bindings. So include them into the scan list below.
- for (int i = 0, size = clientProcesses.size(); i < size; i++) {
- mProcessRecordProcStateNodes.moveAppToTail(clientProcesses.get(i));
- }
- // We don't update the adj list since we're resetting it below.
- }
-
- // Now nodes are set into their slots, without factoring in the bindings.
- // The nodes between the `lastNode` pointer and the TAIL should be the new nodes.
- //
- // The whole rationale here is that, the bindings from client to host app, won't elevate
- // the host app's procstate/adj higher than the client app's state (BIND_ABOVE_CLIENT
- // is a special case here, but client app's raw adj is still no less than the host app's).
- // Therefore, starting from the top to the bottom, for each slot, scan all of the new nodes,
- // check its bindings, elevate its host app's slot if necessary.
- //
- // We'd have to do this in two passes: 1) scan procstate node list; 2) scan adj node list.
- // Because the procstate and adj are not always in sync - there are cases where
- // the processes with lower proc state could be getting a higher oom adj score.
- // And because of this, the procstate and adj node lists are basically two priority heaps.
- //
- // As the 2nd pass with the adj node lists potentially includes a significant amount of
- // duplicated scans as the 1st pass has done, we'll reset the last node pointers for
- // the adj node list before the 1st pass; so during the 1st pass, if any app's adj slot
- // gets bumped, we'll only scan those in 2nd pass.
-
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
mProcessRecordAdjNodes.resetLastNodes();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
+ computeConnectionsLSP();
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime);
+ }
+
+ /**
+ * Traverse the process graph and update processes based on changes in connection importances.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private void computeConnectionsLSP() {
// 1st pass, scan each slot in the procstate node list.
for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) {
- mProcessRecordProcStateNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
// 2nd pass, scan each slot in the adj node list.
for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) {
- mProcessRecordAdjNodes.forEachNewNode(i, this::computeHostOomAdjLSP);
+ mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer);
}
}
- @GuardedBy({"mService", "mProcLock"})
- private void computeInitialOomAdjLSP(OomAdjusterArgs args) {
- final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
- final ProcessRecord topApp = args.mTopApp;
- final long now = args.mNow;
- final int oomAdjReason = args.mOomAdjReason;
- final ActiveUids uids = args.mUids;
- final boolean fullUpdate = args.mFullUpdate;
-
- if (DEBUG_OOM_ADJ) {
- Slog.i(TAG, "OOM ADJ initial args app=" + app
- + " cachedAdj=" + cachedAdj
- + " topApp=" + topApp
- + " now=" + now
- + " oomAdjReason=" + oomAdjReasonToString(oomAdjReason)
- + " fullUpdate=" + fullUpdate);
- }
-
- if (uids != null) {
- final UidRecord uidRec = app.getUidRecord();
-
- if (uidRec != null) {
- uids.put(uidRec.getUid(), uidRec);
- }
- }
-
- computeOomAdjLSP(app, cachedAdj, topApp, fullUpdate, now, false, false, oomAdjReason,
- false);
- }
-
/**
- * @return The proposed change to the schedGroup.
+ * Perform a partial update on the target processes and their reachable processes.
*/
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected int setIntermediateAdjLSP(ProcessRecord app, int adj, int prevRawAppAdj,
- int schedGroup) {
- schedGroup = super.setIntermediateAdjLSP(app, adj, prevRawAppAdj, schedGroup);
+ private void partialUpdateLSP(@OomAdjReason int oomAdjReason, ArraySet<ProcessRecord> targets) {
+ final ProcessRecord topApp = mService.getTopApp();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - mConstants.mMaxEmptyTimeMillis;
- updateAdjSlotIfNecessary(app, prevRawAppAdj);
+ ActiveUids activeUids = mTmpUidRecords;
+ activeUids.clear();
+ mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, activeUids, false);
- return schedGroup;
+ mAdjSeq++;
+
+ final ArrayList<ProcessRecord> reachables = mTmpProcessList;
+ reachables.clear();
+
+ for (int i = 0, size = targets.size(); i < size; i++) {
+ final ProcessRecord target = targets.valueAtUnchecked(i);
+ target.mState.resetCachedInfo();
+ target.mState.setReachable(true);
+ reachables.add(target);
+ }
+
+ // Collect all processes that are reachable.
+ // Any process not found in this step will not change in importance during this update.
+ collectAndMarkReachableProcessesLSP(reachables);
+
+ // Initialize the reachable processes based on their own values plus any
+ // connections from processes not found in the previous step. Since those non-reachable
+ // processes cannot change as a part of this update, their current values can be used
+ // right now.
+ mProcessRecordProcStateNodes.resetLastNodes();
+ initReachableStatesLSP(reachables, mTmpOomAdjusterArgs);
+
+ // Set adj last nodes now, this way a process will only be reevaluated during the adj node
+ // iteration if they adj score changed during the procState node iteration.
+ mProcessRecordAdjNodes.resetLastNodes();
+ // Now traverse and compute the connections of processes with changed importance.
+ computeConnectionsLSP();
+
+ boolean unassignedAdj = false;
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessStateRecord state = reachables.get(i).mState;
+ state.setReachable(false);
+ state.setCompletedAdjSeq(mAdjSeq);
+ if (state.getCurAdj() >= UNKNOWN_ADJ) {
+ unassignedAdj = true;
+ }
+ }
+
+ // If all processes have an assigned adj, no need to calculate and assign cached adjs.
+ if (unassignedAdj) {
+ // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
+ assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ }
+
+ // Repopulate any uid record that may have changed.
+ for (int i = 0, size = activeUids.size(); i < size; i++) {
+ final UidRecord ur = activeUids.valueAt(i);
+ ur.reset();
+ for (int j = ur.getNumOfProcs() - 1; j >= 0; j--) {
+ final ProcessRecord proc = ur.getProcessRecordByIndex(j);
+ updateAppUidRecIfNecessaryLSP(proc);
+ }
+ }
+
+ postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime);
}
+ /**
+ * Mark all processes reachable from the {@code reachables} processes and add them to the
+ * provided {@code reachables} list (targets excluded).
+ *
+ * Returns true if a cycle exists within the reachable process graph.
+ */
@GuardedBy({"mService", "mProcLock"})
- @Override
- protected void setIntermediateProcStateLSP(ProcessRecord app, int procState,
- int prevProcState) {
- super.setIntermediateProcStateLSP(app, procState, prevProcState);
-
- updateProcStateSlotIfNecessary(app, prevProcState);
+ private void collectAndMarkReachableProcessesLSP(ArrayList<ProcessRecord> reachables) {
+ mReachableCollectingConsumer.init(reachables);
+ for (int i = 0; i < reachables.size(); i++) {
+ ProcessRecord pr = reachables.get(i);
+ forEachConnectionLSP(pr, mReachableCollectingConsumer);
+ }
}
+ /**
+ * Calculate initial importance states for {@code reachables} and update their slot position
+ * if necessary.
+ */
+ private void initReachableStatesLSP(ArrayList<ProcessRecord> reachables, OomAdjusterArgs args) {
+ for (int i = 0, size = reachables.size(); i < size; i++) {
+ final ProcessRecord reachable = reachables.get(i);
+ final int prevProcState = reachable.mState.getCurProcState();
+ final int prevAdj = reachable.mState.getCurRawAdj();
+
+ args.mApp = reachable;
+ computeOomAdjIgnoringReachablesLSP(args);
+
+ updateProcStateSlot(reachable, prevProcState);
+ updateAdjSlot(reachable, prevAdj);
+ }
+ }
+
+ /**
+ * Calculate initial importance states for {@code app}.
+ * Processes not marked reachable cannot change as a part of this update, so connections from
+ * those process can be calculated now.
+ */
@GuardedBy({"mService", "mProcLock"})
- private void computeHostOomAdjLSP(OomAdjusterArgs args) {
+ private void computeOomAdjIgnoringReachablesLSP(OomAdjusterArgs args) {
final ProcessRecord app = args.mApp;
- final int cachedAdj = args.mCachedAdj;
final ProcessRecord topApp = args.mTopApp;
final long now = args.mNow;
final @OomAdjReason int oomAdjReason = args.mOomAdjReason;
- final boolean fullUpdate = args.mFullUpdate;
- final ActiveUids uids = args.mUids;
+ computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, false, now, false, false, oomAdjReason, false);
+
+ mComputeConnectionIgnoringReachableClientsConsumer.args = args;
+ forEachClientConnectionLSP(app, mComputeConnectionIgnoringReachableClientsConsumer);
+ }
+
+ /**
+ * Stream the connections with {@code app} as a client to
+ * {@code connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
final ProcessServiceRecord psr = app.mServices;
for (int i = psr.numberOfConnections() - 1; i >= 0; i--) {
ConnectionRecord cr = psr.getConnectionAt(i);
@@ -987,16 +976,14 @@
? cr.binding.service.isolationHostProc : cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)
|| (service.isSdkSandbox && cr.binding.attributedClient != null)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
for (int i = psr.numberOfSdkSandboxConnections() - 1; i >= 0; i--) {
@@ -1004,15 +991,13 @@
final ProcessRecord service = cr.binding.service.app;
if (service == null || service == app
|| (service.mState.getMaxAdj() >= SYSTEM_ADJ
- && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && service.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (service.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && service.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && service.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
-
- computeServiceHostOomAdjLSP(cr, service, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ connectionConsumer.accept(cr, service);
}
final ProcessProviderRecord ppr = app.mProviders;
@@ -1021,15 +1006,51 @@
ProcessRecord provider = cpc.provider.proc;
if (provider == null || provider == app
|| (provider.mState.getMaxAdj() >= ProcessList.SYSTEM_ADJ
- && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
+ && provider.mState.getMaxAdj() < FOREGROUND_APP_ADJ)
|| (provider.mState.getCurAdj() <= FOREGROUND_APP_ADJ
- && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
- && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
+ && provider.mState.getCurrentSchedulingGroup() > SCHED_GROUP_BACKGROUND
+ && provider.mState.getCurProcState() <= PROCESS_STATE_TOP)) {
continue;
}
+ connectionConsumer.accept(cpc, provider);
+ }
+ }
- computeProviderHostOomAdjLSP(cpc, provider, app, now, topApp, fullUpdate, false, false,
- oomAdjReason, cachedAdj, false, false);
+ /**
+ * Stream the connections from clients with {@code app} as the host to {@code
+ * connectionConsumer}.
+ */
+ @GuardedBy({"mService", "mProcLock"})
+ private static void forEachClientConnectionLSP(ProcessRecord app,
+ BiConsumer<Connection, ProcessRecord> connectionConsumer) {
+ final ProcessServiceRecord psr = app.mServices;
+
+ for (int i = psr.numberOfRunningServices() - 1; i >= 0; i--) {
+ final ServiceRecord s = psr.getRunningServiceAt(i);
+ final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
+ s.getConnections();
+ for (int j = serviceConnections.size() - 1; j >= 0; j--) {
+ final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
+ for (int k = clist.size() - 1; k >= 0; k--) {
+ final ConnectionRecord cr = clist.get(k);
+ final ProcessRecord client;
+ if (app.isSdkSandbox && cr.binding.attributedClient != null) {
+ client = cr.binding.attributedClient;
+ } else {
+ client = cr.binding.client;
+ }
+ connectionConsumer.accept(cr, client);
+ }
+ }
+ }
+
+ final ProcessProviderRecord ppr = app.mProviders;
+ for (int i = ppr.numberOfProviders() - 1; i >= 0; i--) {
+ final ContentProviderRecord cpr = ppr.getProviderAt(i);
+ for (int j = cpr.connections.size() - 1; j >= 0; j--) {
+ final ContentProviderConnection conn = cpr.connections.get(j);
+ connectionConsumer.accept(conn, conn.client);
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 2ef433c..0aa1a69 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -718,9 +718,7 @@
mService.mContext, mApp.info.packageName, mApp.info.flags);
}
}
- for (BroadcastQueue queue : mService.mBroadcastQueues) {
- queue.onApplicationProblemLocked(mApp);
- }
+ mService.getBroadcastQueue().onApplicationProblemLocked(mApp);
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 89c8994..a1fdd50 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -208,7 +208,7 @@
// Number of levels we have available for different service connection group importance
// levels.
- static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
+ public static final int CACHED_APP_IMPORTANCE_LEVELS = 5;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7009bd0..7356588 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -69,10 +69,8 @@
import com.android.server.wm.WindowProcessListener;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.function.Consumer;
/**
* Full information about a particular process that
@@ -1659,34 +1657,4 @@
&& !mOptRecord.shouldNotFreeze()
&& mState.getCurAdj() >= ProcessList.FREEZER_CUTOFF_ADJ;
}
-
- /**
- * Traverses all client processes and feed them to consumer.
- */
- @GuardedBy("mProcLock")
- void forEachClient(@NonNull Consumer<ProcessRecord> consumer) {
- for (int i = mServices.numberOfRunningServices() - 1; i >= 0; i--) {
- final ServiceRecord s = mServices.getRunningServiceAt(i);
- final ArrayMap<IBinder, ArrayList<ConnectionRecord>> serviceConnections =
- s.getConnections();
- for (int j = serviceConnections.size() - 1; j >= 0; j--) {
- final ArrayList<ConnectionRecord> clist = serviceConnections.valueAt(j);
- for (int k = clist.size() - 1; k >= 0; k--) {
- final ConnectionRecord cr = clist.get(k);
- if (isSdkSandbox && cr.binding.attributedClient != null) {
- consumer.accept(cr.binding.attributedClient);
- } else {
- consumer.accept(cr.binding.client);
- }
- }
- }
- }
- for (int i = mProviders.numberOfProviders() - 1; i >= 0; i--) {
- final ContentProviderRecord cpr = mProviders.getProviderAt(i);
- for (int j = cpr.connections.size() - 1; j >= 0; j--) {
- final ContentProviderConnection conn = cpr.connections.get(j);
- consumer.accept(conn.client);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java
index 562beaf..3d695bc 100644
--- a/services/core/java/com/android/server/am/ProcessServiceRecord.java
+++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java
@@ -541,7 +541,7 @@
private void removeSdkSandboxConnectionIfNecessary(ConnectionRecord connection) {
final ProcessRecord attributedClient = connection.binding.attributedClient;
if (attributedClient != null && connection.binding.service.isSdkSandbox) {
- if (attributedClient.mServices.mSdkSandboxConnections == null) {
+ if (attributedClient.mServices.mSdkSandboxConnections != null) {
attributedClient.mServices.mSdkSandboxConnections.remove(connection);
}
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3abfe082..13a1807 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1419,7 +1419,8 @@
private boolean allowBiometricUnlockForPrivateProfile() {
return android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index c06bdf9..b1823b4 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -23,6 +23,13 @@
}
flag {
+ name: "fgs_disable_saw"
+ namespace: "backstage_power"
+ description: "Disable System Alert Window FGS start"
+ bug: "296558535"
+}
+
+flag {
name: "bfgs_managed_network_access"
namespace: "backstage_power"
description: "Restrict network access for certain applications in BFGS process state"
diff --git a/services/core/java/com/android/server/biometrics/log/ALSProbe.java b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
index d584c99..d4e46a9 100644
--- a/services/core/java/com/android/server/biometrics/log/ALSProbe.java
+++ b/services/core/java/com/android/server/biometrics/log/ALSProbe.java
@@ -179,15 +179,18 @@
nextConsumer.consume(current);
} else if (mNextConsumer != null) {
mNextConsumer.add(nextConsumer);
- } else {
+ } else if (mLightSensor != null) {
mDestroyed = false;
mNextConsumer = nextConsumer;
enableLightSensorLoggingLocked();
+ } else {
+ Slog.w(TAG, "No light sensor - use current to consume");
+ nextConsumer.consume(current);
}
}
private void enableLightSensorLoggingLocked() {
- if (!mEnabled) {
+ if (!mEnabled && mLightSensor != null) {
mEnabled = true;
mLastAmbientLux = -1;
mSensorManager.registerListener(mLightSensorListener, mLightSensor,
@@ -201,7 +204,7 @@
private void disableLightSensorLoggingLocked(boolean destroying) {
resetTimerLocked(false /* start */);
- if (mEnabled) {
+ if (mEnabled && mLightSensor != null) {
mEnabled = false;
if (!destroying) {
mLastAmbientLux = -1;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6b8586a..7ee2a7a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -119,7 +119,7 @@
* Receives the incoming binder calls from FaceManager.
*/
@VisibleForTesting final class FaceServiceWrapper extends IFaceService.Stub {
- @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
@NonNull String opPackageName) {
@@ -149,11 +149,7 @@
return proto.getBytes();
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public List<FaceSensorPropertiesInternal> getSensorPropertiesInternal(
String opPackageName) {
@@ -297,29 +293,6 @@
restricted, statsClient, isKeyguard);
}
- @android.annotation.EnforcePermission(
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION)
- @Override // Binder call
- public long authenticateInBackground(final IBinder token, final long operationId,
- final IFaceServiceReceiver receiver, final FaceAuthenticateOptions options) {
- // TODO(b/152413782): If the sensor supports face detect and the device is encrypted or
- // lockdown, something wrong happened. See similar path in FingerprintService.
-
- super.authenticateInBackground_enforcePermission();
-
- final Pair<Integer, ServiceProvider> provider = mRegistry.getSingleProvider();
- if (provider == null) {
- Slog.w(TAG, "Null provider for authenticate");
- return -1;
- }
- options.setSensorId(provider.first);
-
- return provider.second.scheduleAuthenticate(token, operationId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), options,
- false /* restricted */, BiometricsProtoEnums.CLIENT_UNKNOWN /* statsClient */,
- true /* allowBackgroundAuthentication */);
- }
-
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public long detectFace(final IBinder token,
@@ -583,11 +556,7 @@
return provider.getEnrolledFaces(sensorId, userId);
}
- @android.annotation.EnforcePermission(
- anyOf = {
- android.Manifest.permission.USE_BIOMETRIC_INTERNAL,
- android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION
- })
+ @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override // Binder call
public boolean hasEnrolledFaces(int sensorId, int userId, String opPackageName) {
super.hasEnrolledFaces_enforcePermission();
diff --git a/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
new file mode 100644
index 0000000..413020e
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ArcClipboardMonitor.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.clipboard;
+
+import android.annotation.Nullable;
+import android.content.ClipData;
+
+import com.android.server.LocalServices;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class ArcClipboardMonitor implements Consumer<ClipData> {
+ private static final String TAG = "ArcClipboardMonitor";
+
+ public interface ArcClipboardBridge {
+ /**
+ * Called when a clipboard content is updated.
+ */
+ void onPrimaryClipChanged(ClipData data);
+
+ /**
+ * Passes the callback to set a new clipboard content with a uid.
+ */
+ void setHandler(BiConsumer<ClipData, Integer> setAndroidClipboard);
+ }
+
+ private ArcClipboardBridge mBridge;
+ private BiConsumer<ClipData, Integer> mAndroidClipboardSetter;
+
+ ArcClipboardMonitor(final BiConsumer<ClipData, Integer> setAndroidClipboard) {
+ mAndroidClipboardSetter = setAndroidClipboard;
+ LocalServices.addService(ArcClipboardMonitor.class, this);
+ }
+
+ @Override
+ public void accept(final @Nullable ClipData clip) {
+ if (mBridge != null) {
+ mBridge.onPrimaryClipChanged(clip);
+ }
+ }
+
+ /**
+ * Sets the other end of the clipboard bridge.
+ */
+ public void setClipboardBridge(ArcClipboardBridge bridge) {
+ mBridge = bridge;
+ mBridge.setHandler(mAndroidClipboardSetter);
+ }
+}
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 49f6070..4c3020f 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -151,7 +151,7 @@
private final ContentCaptureManagerInternal mContentCaptureInternal;
private final AutofillManagerInternal mAutofillInternal;
private final IBinder mPermissionOwner;
- private final Consumer<ClipData> mEmulatorClipboardMonitor;
+ private final Consumer<ClipData> mClipboardMonitor;
private final Handler mWorkerHandler;
@GuardedBy("mLock")
@@ -192,7 +192,7 @@
final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
mPermissionOwner = permOwner;
if (Build.IS_EMULATOR) {
- mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
+ mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
synchronized (mLock) {
Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
if (clipboard != null) {
@@ -201,8 +201,12 @@
}
}
});
+ } else if (Build.IS_ARC) {
+ mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
+ setPrimaryClipInternal(clip, uid);
+ });
} else {
- mEmulatorClipboardMonitor = (clip) -> {};
+ mClipboardMonitor = (clip) -> {};
}
updateConfig();
@@ -937,7 +941,7 @@
private void setPrimaryClipInternalLocked(
@Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
if (deviceId == DEVICE_ID_DEFAULT) {
- mEmulatorClipboardMonitor.accept(clip);
+ mClipboardMonitor.accept(clip);
}
final int userId = UserHandle.getUserId(uid);
diff --git a/services/core/java/com/android/server/connectivity/TEST_MAPPING b/services/core/java/com/android/server/connectivity/TEST_MAPPING
index 687d4b0..55601bc 100644
--- a/services/core/java/com/android/server/connectivity/TEST_MAPPING
+++ b/services/core/java/com/android/server/connectivity/TEST_MAPPING
@@ -7,7 +7,20 @@
"exclude-annotation": "com.android.testutils.SkipPresubmit"
}
],
- "file_patterns": ["Vpn\\.java", "VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ "file_patterns": ["VpnIkeV2Utils\\.java", "VpnProfileStore\\.java"]
+ },
+ {
+ "name":"FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns":[
+ "Vpn\\.java",
+ "VpnIkeV2Utils\\.java",
+ "VpnProfileStore\\.java"
+ ]
}
],
"presubmit-large": [
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index b2a738f..3d95fee 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -403,15 +403,14 @@
brightnessEvent.setRecommendedBrightness(mScreenAutoBrightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
- | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
if (!mAmbientLuxValid) {
return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
- if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+ if (shouldApplyDozeScaleFactor()) {
return mScreenAutoBrightness * mDozeScaleFactor;
}
return mScreenAutoBrightness;
@@ -434,7 +433,7 @@
float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux,
mForegroundAppPackageName, mForegroundAppCategory);
- if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
+ if (shouldApplyDozeScaleFactor()) {
brightness *= mDozeScaleFactor;
}
@@ -443,8 +442,7 @@
brightnessEvent.setRecommendedBrightness(brightness);
brightnessEvent.setFlags(brightnessEvent.getFlags()
| (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0)
- | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
- ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
+ | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0));
brightnessEvent.setAutoBrightnessMode(getMode());
}
return brightness;
@@ -1263,6 +1261,12 @@
}
}
+ private boolean shouldApplyDozeScaleFactor() {
+ // Don't apply the doze scale factor if we have a designated brightness curve for doze
+ return mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
+ && getMode() != AUTO_BRIGHTNESS_MODE_DOZE;
+ }
+
private class ShortTermModel {
// When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
// user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index a43f93a..b1defe9 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -20,7 +20,6 @@
import android.annotation.Nullable;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import android.os.Trace;
/**
@@ -110,9 +109,18 @@
try {
mDisplayOffloader.stopOffload();
mIsActive = false;
- mDisplayPowerController.setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
+
+ @Override
+ public float getBrightness() {
+ return mDisplayPowerController.getScreenBrightnessSetting();
+ }
+
+ @Override
+ public float getDozeBrightness() {
+ return mDisplayPowerController.getDozeBrightnessForOffload();
+ }
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2010aca..77a43d0 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -375,7 +375,8 @@
// information.
// At the time of this writing, this value is changed within updatePowerState() only, which is
// limited to the thread used by DisplayControllerHandler.
- private final BrightnessReason mBrightnessReason = new BrightnessReason();
+ @VisibleForTesting
+ final BrightnessReason mBrightnessReason = new BrightnessReason();
private final BrightnessReason mBrightnessReasonTemp = new BrightnessReason();
// Brightness animation ramp rates in brightness units per second
@@ -486,6 +487,9 @@
private DisplayOffloadSession mDisplayOffloadSession;
+ // Used to scale the brightness in doze mode
+ private float mDozeScaleFactor;
+
/**
* Creates the display power controller.
*/
@@ -546,6 +550,9 @@
loadBrightnessRampRates();
mSkipScreenOnBrightnessRamp = resources.getBoolean(
R.bool.config_skipScreenOnBrightnessRamp);
+ mDozeScaleFactor = context.getResources().getFraction(
+ R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ 1, 1);
Runnable modeChangeCallback = () -> {
sendUpdatePowerState();
@@ -1041,10 +1048,6 @@
}
if (defaultModeBrightnessMapper != null) {
- final float dozeScaleFactor = context.getResources().getFraction(
- R.fraction.config_screenAutoBrightnessDozeScaleFactor,
- 1, 1);
-
// Ambient Lux - Active Mode Brightness Thresholds
float[] ambientBrighteningThresholds =
mDisplayDeviceConfig.getAmbientBrighteningPercentages();
@@ -1155,7 +1158,7 @@
mAutomaticBrightnessController = mInjector.getAutomaticBrightnessController(
this, handler.getLooper(), mSensorManager, mLightSensor,
brightnessMappers, lightSensorWarmUpTimeConfig, PowerManager.BRIGHTNESS_MIN,
- PowerManager.BRIGHTNESS_MAX, dozeScaleFactor, lightSensorRate,
+ PowerManager.BRIGHTNESS_MAX, mDozeScaleFactor, lightSensorRate,
initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce,
brighteningLightDebounceIdle, darkeningLightDebounceIdle,
autoBrightnessResetAmbientLuxAfterWarmUp, ambientBrightnessThresholds,
@@ -1379,7 +1382,7 @@
// Switch to doze auto-brightness mode if needed
if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
&& !mAutomaticBrightnessController.isInIdleMode()) {
- mAutomaticBrightnessController.switchMode(mPowerRequest.policy == POLICY_DOZE
+ mAutomaticBrightnessController.switchMode(Display.isDozeState(state)
? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
}
@@ -1434,7 +1437,9 @@
float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness();
// Apply auto-brightness.
int brightnessAdjustmentFlags = 0;
- if (Float.isNaN(brightnessState)) {
+ // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy
+ if (Float.isNaN(brightnessState)
+ || mBrightnessReasonTemp.getReason() == BrightnessReason.REASON_OFFLOAD) {
if (mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()) {
brightnessState = mAutomaticBrightnessStrategy.getAutomaticScreenBrightness(
mTempBrightnessEvent);
@@ -1455,8 +1460,11 @@
if (mScreenOffBrightnessSensorController != null) {
mScreenOffBrightnessSensorController.setLightSensorEnabled(false);
}
+ setBrightnessFromOffload(PowerManager.BRIGHTNESS_INVALID_FLOAT);
} else {
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+ // Restore the lower-priority brightness strategy
+ brightnessState = displayBrightnessState.getBrightness();
}
}
} else {
@@ -1467,17 +1475,22 @@
mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
}
- // If there's an offload session and auto-brightness is on, we need to set the initial doze
- // brightness using the doze auto-brightness curve before the offload session starts
- // controlling the brightness.
- if (Float.isNaN(brightnessState) && mFlags.areAutoBrightnessModesEnabled()
- && mFlags.isDisplayOffloadEnabled()
- && mPowerRequest.policy == POLICY_DOZE
- && mDisplayOffloadSession != null
- && mAutomaticBrightnessController != null
- && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
- rawBrightnessState = mAutomaticBrightnessController
- .getAutomaticScreenBrightnessBasedOnLastObservedLux(mTempBrightnessEvent);
+ // If there's an offload session, we need to set the initial doze brightness before
+ // the offload session starts controlling the brightness.
+ if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled()
+ && mPowerRequest.policy == POLICY_DOZE && mDisplayOffloadSession != null) {
+ if (mAutomaticBrightnessController != null
+ && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) {
+ // Use the auto-brightness curve and the last observed lux
+ rawBrightnessState = mAutomaticBrightnessController
+ .getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ mTempBrightnessEvent);
+ } else {
+ rawBrightnessState = getDozeBrightnessForOffload();
+ mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
+ | BrightnessEvent.FLAG_DOZE_SCALE);
+ }
+
if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) {
brightnessState = clampScreenBrightness(rawBrightnessState);
mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL);
@@ -2438,6 +2451,11 @@
}
@Override
+ public float getDozeBrightnessForOffload() {
+ return mDisplayBrightnessController.getCurrentBrightness() * mDozeScaleFactor;
+ }
+
+ @Override
public void setBrightness(float brightness) {
mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness));
}
@@ -2590,6 +2608,7 @@
}
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
+ pw.println(" mDozeScaleFactor=" + mDozeScaleFactor);
mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
}
@@ -3020,9 +3039,10 @@
setDwbcLoggingEnabled(msg.arg1);
break;
case MSG_SET_BRIGHTNESS_FROM_OFFLOAD:
- mDisplayBrightnessController.setBrightnessFromOffload(
- Float.intBitsToFloat(msg.arg1));
- updatePowerState();
+ if (mDisplayBrightnessController.setBrightnessFromOffload(
+ Float.intBitsToFloat(msg.arg1))) {
+ updatePowerState();
+ }
break;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index ecf1635..408d610 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -161,6 +161,11 @@
float getScreenBrightnessSetting();
/**
+ * Gets the brightness value used when the device is in doze
+ */
+ float getDozeBrightnessForOffload();
+
+ /**
* Sets up the temporary brightness for the associated display
*/
void setTemporaryBrightness(float brightness);
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index f6d02db..34d53be 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -26,6 +26,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.server.display.AutomaticBrightnessController;
import com.android.server.display.BrightnessMappingStrategy;
import com.android.server.display.BrightnessSetting;
@@ -175,14 +176,19 @@
/**
* Sets the brightness from the offload session.
+ * @return Whether the offload brightness has changed
*/
- public void setBrightnessFromOffload(float brightness) {
+ public boolean setBrightnessFromOffload(float brightness) {
synchronized (mLock) {
- if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null) {
+ if (mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy() != null
+ && !BrightnessSynchronizer.floatEquals(mDisplayBrightnessStrategySelector
+ .getOffloadBrightnessStrategy().getOffloadScreenBrightness(), brightness)) {
mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()
.setOffloadScreenBrightness(brightness);
+ return true;
}
}
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
index 8e84450..03fb147 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java
@@ -71,8 +71,14 @@
@Nullable
private final OffloadBrightnessStrategy mOffloadBrightnessStrategy;
+ // A collective representation of all the strategies that the selector is aware of. This is
+ // non null, but the strategies this is tracking can be null
+ @NonNull
private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies;
+ @NonNull
+ private final DisplayManagerFlags mDisplayManagerFlags;
+
// We take note of the old brightness strategy so that we can know when the strategy changes.
private String mOldBrightnessStrategyName;
@@ -86,6 +92,7 @@
if (injector == null) {
injector = new Injector();
}
+ mDisplayManagerFlags = flags;
mDisplayId = displayId;
mDozeBrightnessStrategy = injector.getDozeBrightnessStrategy();
mScreenOffBrightnessStrategy = injector.getScreenOffBrightnessStrategy();
@@ -133,11 +140,16 @@
} else if (BrightnessUtils.isValidBrightnessValue(
mTemporaryBrightnessStrategy.getTemporaryScreenBrightness())) {
displayBrightnessStrategy = mTemporaryBrightnessStrategy;
- } else if (mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
+ } else if (mAutomaticBrightnessStrategy.shouldUseAutoBrightness()
+ && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue(
mOffloadBrightnessStrategy.getOffloadScreenBrightness())) {
displayBrightnessStrategy = mOffloadBrightnessStrategy;
}
+ if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) {
+ postProcess(constructStrategySelectionNotifyRequest(displayBrightnessStrategy));
+ }
+
if (!mOldBrightnessStrategyName.equals(displayBrightnessStrategy.getName())) {
Slog.i(TAG,
"Changing the DisplayBrightnessStrategy from " + mOldBrightnessStrategyName
@@ -185,13 +197,27 @@
" mAllowAutoBrightnessWhileDozingConfig= "
+ mAllowAutoBrightnessWhileDozingConfig);
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
- for (DisplayBrightnessStrategy displayBrightnessStrategy: mDisplayBrightnessStrategies) {
+ for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
if (displayBrightnessStrategy != null) {
displayBrightnessStrategy.dump(ipw);
}
}
}
+ private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest(
+ DisplayBrightnessStrategy selectedDisplayBrightnessStrategy) {
+ return new StrategySelectionNotifyRequest(selectedDisplayBrightnessStrategy);
+ }
+
+ private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ for (DisplayBrightnessStrategy displayBrightnessStrategy : mDisplayBrightnessStrategies) {
+ if (displayBrightnessStrategy != null) {
+ displayBrightnessStrategy.strategySelectionPostProcessor(
+ strategySelectionNotifyRequest);
+ }
+ }
+ }
+
/**
* Validates if the conditions are met to qualify for the DozeBrightnessStrategy.
*/
diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
new file mode 100644
index 0000000..d8bd2e4
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness;
+
+import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
+
+import java.util.Objects;
+
+/**
+ * A wrapper class to encapsulate the request to notify the strategies about the selection of a
+ * DisplayBrightnessStrategy
+ */
+public final class StrategySelectionNotifyRequest {
+ // The strategy that was selected with the current request
+ private final DisplayBrightnessStrategy mSelectedDisplayBrightnessStrategy;
+
+ public StrategySelectionNotifyRequest(DisplayBrightnessStrategy displayBrightnessStrategy) {
+ mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy;
+ }
+
+ public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() {
+ return mSelectedDisplayBrightnessStrategy;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StrategySelectionNotifyRequest)) {
+ return false;
+ }
+ StrategySelectionNotifyRequest other = (StrategySelectionNotifyRequest) obj;
+ return other.getSelectedDisplayBrightnessStrategy()
+ == getSelectedDisplayBrightnessStrategy();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSelectedDisplayBrightnessStrategy);
+ }
+}
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
index d1ca49b..8b54b22 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -108,7 +108,6 @@
mIsAutoBrightnessEnabled = shouldUseAutoBrightness()
&& (targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze)
&& brightnessReason != BrightnessReason.REASON_OVERRIDE
- && brightnessReason != BrightnessReason.REASON_OFFLOAD
&& mAutomaticBrightnessController != null;
mAutoBrightnessDisabledDueToDisplayOff = shouldUseAutoBrightness()
&& !(targetDisplayState == Display.STATE_ON || autoBrightnessEnabledInDoze);
diff --git a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
index 9ee1d73..11edde9 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/BoostBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -53,4 +54,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
index 1f28eb4..7b49957 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategy.java
@@ -20,6 +20,7 @@
import android.hardware.display.DisplayManagerInternal;
import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -48,4 +49,10 @@
* @param writer
*/
void dump(PrintWriter writer);
+
+ /**
+ * Notifies this strategy about the selection of a DisplayBrightnessStrategy
+ */
+ void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest);
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
index 2be7443..5afdc42 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/DozeBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -46,4 +47,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
index 54f9afc..0650c1c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FollowerBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -82,4 +83,10 @@
writer.println(" mBrightnessToFollow:" + mBrightnessToFollow);
writer.println(" mBrightnessToFollowSlowChange:" + mBrightnessToFollowSlowChange);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
index 49c3e03..bf37ee0 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/InvalidBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -44,4 +45,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
index 4ffb16b..d2bb1e2 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OffloadBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -72,4 +73,10 @@
writer.println("OffloadBrightnessStrategy:");
writer.println(" mOffloadScreenBrightness:" + mOffloadScreenBrightness);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
index 7b651d8..653170c 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/OverrideBrightnessStrategy.java
@@ -21,6 +21,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -45,4 +46,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
index 201ef41..f0cce23 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -46,4 +47,10 @@
@Override
public void dump(PrintWriter writer) {}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
index bbd0c00..91e1d09 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategy.java
@@ -22,6 +22,7 @@
import com.android.server.display.DisplayBrightnessState;
import com.android.server.display.brightness.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils;
+import com.android.server.display.brightness.StrategySelectionNotifyRequest;
import java.io.PrintWriter;
@@ -73,4 +74,10 @@
writer.println("TemporaryBrightnessStrategy:");
writer.println(" mTemporaryScreenBrightness:" + mTemporaryScreenBrightness);
}
+
+ @Override
+ public void strategySelectionPostProcessor(
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest) {
+ // DO NOTHING
+ }
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 516d4b1..3c98ee4 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -126,6 +126,13 @@
Flags::sensorBasedBrightnessThrottling
);
+
+ private final FlagState mRefactorDisplayPowerController = new FlagState(
+ Flags.FLAG_REFACTOR_DISPLAY_POWER_CONTROLLER,
+ Flags::refactorDisplayPowerController
+ );
+
+
/**
* @return {@code true} if 'port' is allowed in display layout configuration file.
*/
@@ -256,6 +263,10 @@
return mSensorBasedBrightnessThrottling.isEnabled();
}
+ public boolean isRefactorDisplayPowerControllerEnabled() {
+ return mRefactorDisplayPowerController.isEnabled();
+ }
+
/**
* dumps all flagstates
* @param pw printWriter
@@ -280,6 +291,7 @@
pw.println(" " + mFastHdrTransitions);
pw.println(" " + mRefreshRateVotingTelemetry);
pw.println(" " + mSensorBasedBrightnessThrottling);
+ pw.println(" " + mRefactorDisplayPowerController);
}
private static class FlagState {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 63ab3a9..3404527 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -192,3 +192,11 @@
bug: "294900859"
is_fixed_read_only: true
}
+
+flag {
+ name: "refactor_display_power_controller"
+ namespace: "display_manager"
+ description: "Feature flag for refactoring the DisplayPowerController and associated components"
+ bug: "294444204"
+ is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
new file mode 100644
index 0000000..10b5eff
--- /dev/null
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.flags"
+
+flag {
+ namespace: "wear_frameworks"
+ name: "enable_odp_feature_guard"
+ description: "Enable guard based on system feature to prevent OnDevicePersonalization service from starting on form factors."
+ bug: "322249125"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index a23c08a..d876a38 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -198,7 +198,7 @@
addValidationInfo(Constants.MESSAGE_CEC_VERSION,
oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT);
addValidationInfo(Constants.MESSAGE_SET_MENU_LANGUAGE,
- new AsciiValidator(3), ADDR_NOT_UNREGISTERED, ADDR_BROADCAST);
+ new AsciiValidator(3), ADDR_TV, ADDR_BROADCAST);
ParameterValidator statusRequestValidator = new MinimumOneByteRangeValidator(0x01, 0x03);
addValidationInfo(Constants.MESSAGE_DECK_CONTROL,
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index c8c66238..d0532b99 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -4665,15 +4665,27 @@
public void onAudioDeviceVolumeChanged(
@NonNull AudioDeviceAttributes audioDevice,
@NonNull VolumeInfo volumeInfo) {
+ int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
- // Do nothing if the System Audio device does not support <Set Audio Volume Level>
+ // We can't send <Set Audio Volume Level> if the System Audio device doesn't support it.
+ // But AudioService has already updated its volume and expects us to set it.
+ // So the best we can do is to send <Give Audio Status>, which triggers
+ // <Report Audio Status>, which should update AudioService with its correct volume.
if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
!= DeviceFeatures.FEATURE_SUPPORTED) {
+ // Update the volume tracked in AbsoluteVolumeAudioStatusAction
+ // so it correctly processes the next <Report Audio Status>
+ HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback();
+ avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex());
+ // Send <Give Audio Status>
+ sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus(
+ localDeviceAddress,
+ mSystemAudioDevice.getLogicalAddress()
+ ));
return;
}
// Send <Set Audio Volume Level> to notify the System Audio device of the volume change
- int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress();
sendCecCommand(SetAudioVolumeLevelMessage.build(
localDeviceAddress,
mSystemAudioDevice.getLogicalAddress(),
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a79e771..05b1cb69 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -54,6 +54,7 @@
import android.hardware.input.InputSensorInfo;
import android.hardware.input.InputSettings;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
@@ -1244,9 +1245,9 @@
}
@Override // Binder call
- public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
return mKeyboardLayoutManager.getKeyboardLayoutForInputDevice(identifier, userId,
imeInfo, imeSubtype);
}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 46668de..283e692 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -16,10 +16,11 @@
package com.android.server.input;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER;
-import static com.android.server.input.KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.FAILED;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
import android.annotation.AnyThread;
import android.annotation.MainThread;
@@ -46,6 +47,7 @@
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult;
import android.icu.lang.UScript;
import android.icu.util.ULocale;
import android.os.Bundle;
@@ -79,7 +81,6 @@
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.KeyboardMetricsCollector.KeyboardConfigurationEvent;
-import com.android.server.input.KeyboardMetricsCollector.LayoutSelectionCriteria;
import com.android.server.inputmethod.InputMethodManagerInternal;
import libcore.io.Streams;
@@ -130,7 +131,8 @@
// This cache stores "best-matched" layouts so that we don't need to run the matching
// algorithm repeatedly.
@GuardedBy("mKeyboardLayoutCache")
- private final Map<String, KeyboardLayoutInfo> mKeyboardLayoutCache = new ArrayMap<>();
+ private final Map<String, KeyboardLayoutSelectionResult> mKeyboardLayoutCache =
+ new ArrayMap<>();
private final Object mImeInfoLock = new Object();
@Nullable
@GuardedBy("mImeInfoLock")
@@ -222,17 +224,17 @@
} else {
Set<String> selectedLayouts = new HashSet<>();
List<ImeInfo> imeInfoList = getImeInfoListForLayoutMapping();
- List<KeyboardLayoutInfo> layoutInfoList = new ArrayList<>();
+ List<KeyboardLayoutSelectionResult> resultList = new ArrayList<>();
boolean hasMissingLayout = false;
for (ImeInfo imeInfo : imeInfoList) {
// Check if the layout has been previously configured
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
keyboardIdentifier, imeInfo);
- boolean noLayoutFound = layoutInfo == null || layoutInfo.mDescriptor == null;
+ boolean noLayoutFound = result.getLayoutDescriptor() == null;
if (!noLayoutFound) {
- selectedLayouts.add(layoutInfo.mDescriptor);
+ selectedLayouts.add(result.getLayoutDescriptor());
}
- layoutInfoList.add(layoutInfo);
+ resultList.add(result);
hasMissingLayout |= noLayoutFound;
}
@@ -260,7 +262,7 @@
}
if (shouldLogConfiguration) {
- logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
+ logKeyboardConfigurationEvent(inputDevice, imeInfoList, resultList,
!mDataStore.hasInputDeviceEntry(key));
}
} finally {
@@ -757,10 +759,10 @@
String keyboardLayoutDescriptor;
if (useNewSettingsUi()) {
synchronized (mImeInfoLock) {
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
new KeyboardIdentifier(identifier, languageTag, layoutType),
mCurrentImeInfo);
- keyboardLayoutDescriptor = layoutInfo == null ? null : layoutInfo.mDescriptor;
+ keyboardLayoutDescriptor = result.getLayoutDescriptor();
}
} else {
keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
@@ -788,26 +790,26 @@
}
@AnyThread
- @Nullable
- public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
- @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
- @Nullable InputMethodSubtype imeSubtype) {
+ @NonNull
+ public KeyboardLayoutSelectionResult getKeyboardLayoutForInputDevice(
+ InputDeviceIdentifier identifier, @UserIdInt int userId,
+ @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) {
if (!useNewSettingsUi()) {
Slog.e(TAG, "getKeyboardLayoutForInputDevice() API not supported");
- return null;
+ return FAILED;
}
InputDevice inputDevice = getInputDevice(identifier);
if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
- return null;
+ return FAILED;
}
KeyboardIdentifier keyboardIdentifier = new KeyboardIdentifier(inputDevice);
- KeyboardLayoutInfo layoutInfo = getKeyboardLayoutForInputDeviceInternal(
+ KeyboardLayoutSelectionResult result = getKeyboardLayoutForInputDeviceInternal(
keyboardIdentifier, new ImeInfo(userId, imeInfo, imeSubtype));
if (DEBUG) {
Slog.d(TAG, "getKeyboardLayoutForInputDevice() " + identifier.toString() + ", userId : "
- + userId + ", subtype = " + imeSubtype + " -> " + layoutInfo);
+ + userId + ", subtype = " + imeSubtype + " -> " + result);
}
- return layoutInfo != null ? layoutInfo.mDescriptor : null;
+ return result;
}
@AnyThread
@@ -942,13 +944,13 @@
}
@Nullable
- private KeyboardLayoutInfo getKeyboardLayoutForInputDeviceInternal(
+ private KeyboardLayoutSelectionResult getKeyboardLayoutForInputDeviceInternal(
KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo) {
String layoutKey = new LayoutKey(keyboardIdentifier, imeInfo).toString();
synchronized (mDataStore) {
String layout = mDataStore.getKeyboardLayout(keyboardIdentifier.toString(), layoutKey);
if (layout != null) {
- return new KeyboardLayoutInfo(layout, LAYOUT_SELECTION_CRITERIA_USER);
+ return new KeyboardLayoutSelectionResult(layout, LAYOUT_SELECTION_CRITERIA_USER);
}
}
@@ -961,17 +963,17 @@
KeyboardLayout[] layoutList = getKeyboardLayoutListForInputDeviceInternal(
keyboardIdentifier, imeInfo);
// Call auto-matching algorithm to find the best matching layout
- KeyboardLayoutInfo layoutInfo =
+ KeyboardLayoutSelectionResult result =
getDefaultKeyboardLayoutBasedOnImeInfo(keyboardIdentifier, imeInfo,
layoutList);
- mKeyboardLayoutCache.put(layoutKey, layoutInfo);
- return layoutInfo;
+ mKeyboardLayoutCache.put(layoutKey, result);
+ return result;
}
}
}
- @Nullable
- private static KeyboardLayoutInfo getDefaultKeyboardLayoutBasedOnImeInfo(
+ @NonNull
+ private static KeyboardLayoutSelectionResult getDefaultKeyboardLayoutBasedOnImeInfo(
KeyboardIdentifier keyboardIdentifier, @Nullable ImeInfo imeInfo,
KeyboardLayout[] layoutList) {
Arrays.sort(layoutList);
@@ -986,7 +988,7 @@
+ "vendor and product Ids. " + keyboardIdentifier
+ " : " + layout.getDescriptor());
}
- return new KeyboardLayoutInfo(layout.getDescriptor(),
+ return new KeyboardLayoutSelectionResult(layout.getDescriptor(),
LAYOUT_SELECTION_CRITERIA_DEVICE);
}
}
@@ -1004,13 +1006,14 @@
+ "HW information (Language tag and Layout type). "
+ keyboardIdentifier + " : " + layoutDesc);
}
- return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_DEVICE);
+ return new KeyboardLayoutSelectionResult(layoutDesc,
+ LAYOUT_SELECTION_CRITERIA_DEVICE);
}
}
if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
// Can't auto select layout based on IME info is null
- return null;
+ return FAILED;
}
InputMethodSubtype subtype = imeInfo.mImeSubtype;
@@ -1027,9 +1030,10 @@
+ layoutDesc);
}
if (layoutDesc != null) {
- return new KeyboardLayoutInfo(layoutDesc, LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
+ return new KeyboardLayoutSelectionResult(layoutDesc,
+ LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD);
}
- return null;
+ return FAILED;
}
@Nullable
@@ -1246,21 +1250,23 @@
}
private void logKeyboardConfigurationEvent(@NonNull InputDevice inputDevice,
- @NonNull List<ImeInfo> imeInfoList, @NonNull List<KeyboardLayoutInfo> layoutInfoList,
+ @NonNull List<ImeInfo> imeInfoList,
+ @NonNull List<KeyboardLayoutSelectionResult> resultList,
boolean isFirstConfiguration) {
- if (imeInfoList.isEmpty() || layoutInfoList.isEmpty()) {
+ if (imeInfoList.isEmpty() || resultList.isEmpty()) {
return;
}
KeyboardConfigurationEvent.Builder configurationEventBuilder =
new KeyboardConfigurationEvent.Builder(inputDevice).setIsFirstTimeConfiguration(
isFirstConfiguration);
for (int i = 0; i < imeInfoList.size(); i++) {
- KeyboardLayoutInfo layoutInfo = layoutInfoList.get(i);
+ KeyboardLayoutSelectionResult result = resultList.get(i);
String layoutName = null;
int layoutSelectionCriteria = LAYOUT_SELECTION_CRITERIA_DEFAULT;
- if (layoutInfo != null && layoutInfo.mDescriptor != null) {
- layoutSelectionCriteria = layoutInfo.mSelectionCriteria;
- KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(layoutInfo.mDescriptor);
+ if (result != null && result.getLayoutDescriptor() != null) {
+ layoutSelectionCriteria = result.getSelectionCriteria();
+ KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(
+ result.getLayoutDescriptor());
if (d != null) {
layoutName = d.keyboardLayoutName;
}
@@ -1477,33 +1483,6 @@
}
}
- private static class KeyboardLayoutInfo {
- @Nullable
- private final String mDescriptor;
- @LayoutSelectionCriteria
- private final int mSelectionCriteria;
-
- private KeyboardLayoutInfo(@Nullable String descriptor,
- @LayoutSelectionCriteria int selectionCriteria) {
- mDescriptor = descriptor;
- mSelectionCriteria = selectionCriteria;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof KeyboardLayoutInfo) {
- return Objects.equals(mDescriptor, ((KeyboardLayoutInfo) obj).mDescriptor)
- && mSelectionCriteria == ((KeyboardLayoutInfo) obj).mSelectionCriteria;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return 31 * mSelectionCriteria + mDescriptor.hashCode();
- }
- }
-
private interface KeyboardLayoutVisitor {
void visitKeyboardLayout(Resources resources,
int keyboardLayoutResId, KeyboardLayout layout);
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index f53b941..b8ae737 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -16,14 +16,18 @@
package com.android.server.input;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD;
+import static android.hardware.input.KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT;
+import static android.hardware.input.KeyboardLayoutSelectionResult.layoutSelectionCriteriaToString;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.role.RoleManager;
import android.content.Intent;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
import android.icu.util.ULocale;
import android.text.TextUtils;
import android.util.Log;
@@ -40,7 +44,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.policy.ModifierShortcutManager;
-import java.lang.annotation.Retention;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -57,32 +60,6 @@
// (requires restart)
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- @Retention(SOURCE)
- @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
- LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
- LAYOUT_SELECTION_CRITERIA_USER,
- LAYOUT_SELECTION_CRITERIA_DEVICE,
- LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
- LAYOUT_SELECTION_CRITERIA_DEFAULT
- })
- public @interface LayoutSelectionCriteria {
- }
-
- /** Unspecified layout selection criteria */
- public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
-
- /** Manual selection by user */
- public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
-
- /** Auto-detection based on device provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
-
- /** Auto-detection based on IME provided language tag and layout type */
- public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
-
- /** Default selection */
- public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
-
@VisibleForTesting
static final String DEFAULT_LAYOUT_NAME = "Default";
@@ -629,30 +606,16 @@
@Override
public String toString() {
- return "{keyboardLanguageTag = " + keyboardLanguageTag + " keyboardLayoutType = "
+ return "{keyboardLanguageTag = " + keyboardLanguageTag
+ + " keyboardLayoutType = "
+ KeyboardLayout.LayoutType.getLayoutNameFromValue(keyboardLayoutType)
- + " keyboardLayoutName = " + keyboardLayoutName + " layoutSelectionCriteria = "
- + getStringForSelectionCriteria(layoutSelectionCriteria)
- + "imeLanguageTag = " + imeLanguageTag + " imeLayoutType = "
- + KeyboardLayout.LayoutType.getLayoutNameFromValue(imeLayoutType) + "}";
- }
- }
-
- private static String getStringForSelectionCriteria(
- @LayoutSelectionCriteria int layoutSelectionCriteria) {
- switch (layoutSelectionCriteria) {
- case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
- return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
- case LAYOUT_SELECTION_CRITERIA_USER:
- return "LAYOUT_SELECTION_CRITERIA_USER";
- case LAYOUT_SELECTION_CRITERIA_DEVICE:
- return "LAYOUT_SELECTION_CRITERIA_DEVICE";
- case LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD:
- return "LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD";
- case LAYOUT_SELECTION_CRITERIA_DEFAULT:
- return "LAYOUT_SELECTION_CRITERIA_DEFAULT";
- default:
- return "INVALID_CRITERIA";
+ + " keyboardLayoutName = " + keyboardLayoutName
+ + " layoutSelectionCriteria = "
+ + layoutSelectionCriteriaToString(layoutSelectionCriteria)
+ + " imeLanguageTag = " + imeLanguageTag
+ + " imeLayoutType = " + KeyboardLayout.LayoutType.getLayoutNameFromValue(
+ imeLayoutType)
+ + "}";
}
}
diff --git a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
index b30f5ec..6eae9a4 100644
--- a/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
+++ b/services/core/java/com/android/server/input/debug/FocusEventDebugView.java
@@ -229,7 +229,8 @@
/** Report a key event to the debug view. */
@AnyThread
public void reportKeyEvent(KeyEvent event) {
- post(() -> handleKeyEvent(KeyEvent.obtain((KeyEvent) event)));
+ KeyEvent keyEvent = KeyEvent.obtain(event);
+ post(() -> handleKeyEvent(keyEvent));
}
/** Report a motion event to the debug view. */
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index 8826e3d..73f1aad 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -91,10 +91,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_SHOW_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mCurFocusedWindow),
+ Objects.toString(mService.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
+ mService.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
statsToken);
@@ -122,10 +122,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_HIDE_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mCurFocusedWindow),
+ Objects.toString(mService.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mCurFocusedWindowSoftInputMode));
+ mService.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
statsToken);
@@ -207,7 +207,8 @@
@Override
public boolean removeImeScreenshot(int displayId) {
if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
- mService.onShowHideSoftInputRequested(false /* show */, mService.mCurFocusedWindow,
+ mService.onShowHideSoftInputRequested(false /* show */,
+ mService.mImeBindingState.mFocusedWindow,
REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */);
return true;
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
new file mode 100644
index 0000000..4c20c3b
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
+import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.util.Printer;
+import android.util.proto.ProtoOutputStream;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * Stores information related to one active IME client on one display.
+ */
+final class ImeBindingState {
+
+ /**
+ * The last window token that we confirmed to be focused. This is always updated upon
+ * reports from the input method client. If the window state is already changed before the
+ * report is handled, this field just keeps the last value.
+ */
+ @Nullable
+ final IBinder mFocusedWindow;
+
+ /**
+ * {@link WindowManager.LayoutParams#softInputMode} of {@link #mFocusedWindow}.
+ *
+ * @see #mFocusedWindow
+ */
+ @SoftInputModeFlags
+ final int mFocusedWindowSoftInputMode;
+
+ /**
+ * The client by which {@link #mFocusedWindow} was reported. This gets updated whenever
+ * an
+ * IME-focusable window gained focus (without necessarily starting an input connection),
+ * while {@link InputMethodManagerService#mClient} only gets updated when we actually start an
+ * input connection.
+ *
+ * @see #mFocusedWindow
+ */
+ @Nullable
+ final ClientState mFocusedWindowClient;
+
+ /**
+ * The editor info by which {@link #mFocusedWindow} was reported. This differs from
+ * {@link InputMethodManagerService#mCurEditorInfo} the same way {@link #mFocusedWindowClient}
+ * differs from {@link InputMethodManagerService#mCurClient}.
+ *
+ * @see #mFocusedWindow
+ */
+ @Nullable
+ final EditorInfo mFocusedWindowEditorInfo;
+
+ void dumpDebug(ProtoOutputStream proto, WindowManagerInternal windowManagerInternal) {
+ proto.write(CUR_FOCUSED_WINDOW_NAME,
+ windowManagerInternal.getWindowName(mFocusedWindow));
+ proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
+ InputMethodDebug.softInputModeToString(mFocusedWindowSoftInputMode));
+ }
+
+ void dump(String prefix, Printer p) {
+ p.println(prefix + "mFocusedWindow()=" + mFocusedWindow);
+ p.println(prefix + "softInputMode=" + InputMethodDebug.softInputModeToString(
+ mFocusedWindowSoftInputMode));
+ p.println(prefix + "mFocusedWindowClient=" + mFocusedWindowClient);
+ }
+
+ static ImeBindingState newEmptyState() {
+ return new ImeBindingState(
+ /*focusedWindow=*/ null,
+ /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED,
+ /*focusedWindowClient=*/ null,
+ /*focusedWindowEditorInfo=*/ null
+ );
+ }
+
+ ImeBindingState(@Nullable IBinder focusedWindow,
+ @SoftInputModeFlags int focusedWindowSoftInputMode,
+ @Nullable ClientState focusedWindowClient,
+ @Nullable EditorInfo focusedWindowEditorInfo) {
+ mFocusedWindow = focusedWindow;
+ mFocusedWindowSoftInputMode = focusedWindowSoftInputMode;
+ mFocusedWindowClient = focusedWindowClient;
+ mFocusedWindowEditorInfo = focusedWindowEditorInfo;
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 743b8e3..cdfde87 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -548,7 +548,7 @@
}
}
// Fallback to the focused window for some edge cases (e.g. relaunching the activity)
- return mService.mCurFocusedWindow;
+ return mService.mImeBindingState.mFocusedWindow;
}
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1564b2f..2205986 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -28,7 +28,6 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT;
-import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_NAME;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID;
import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID;
@@ -40,7 +39,6 @@
import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE;
import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE;
import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME;
-import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID;
import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD;
import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -277,9 +275,6 @@
@NonNull
private final String[] mNonPreemptibleInputMethods;
- @UserIdInt
- private int mLastSwitchUserId;
-
final Context mContext;
final Resources mRes;
private final Handler mHandler;
@@ -423,6 +418,11 @@
private final ClientController mClientController;
/**
+ * Holds the current IME binding state info.
+ */
+ ImeBindingState mImeBindingState;
+
+ /**
* Set once the system is ready to run third party code.
*/
boolean mSystemReady;
@@ -482,13 +482,6 @@
private ClientState mCurClient;
/**
- * The last window token that we confirmed to be focused. This is always updated upon reports
- * from the input method client. If the window state is already changed before the report is
- * handled, this field just keeps the last value.
- */
- IBinder mCurFocusedWindow;
-
- /**
* The last window token that we confirmed that IME started talking to. This is always updated
* upon reports from the input method. If the window state is already changed before the report
* is handled, this field just keeps the last value.
@@ -496,34 +489,6 @@
IBinder mLastImeTargetWindow;
/**
- * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
- *
- * @see #mCurFocusedWindow
- */
- @SoftInputModeFlags
- int mCurFocusedWindowSoftInputMode;
-
- /**
- * The client by which {@link #mCurFocusedWindow} was reported. This gets updated whenever an
- * IME-focusable window gained focus (without necessarily starting an input connection),
- * while {@link #mCurClient} only gets updated when we actually start an input connection.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- ClientState mCurFocusedWindowClient;
-
- /**
- * The editor info by which {@link #mCurFocusedWindow} was reported. This differs from
- * {@link #mCurEditorInfo} the same way {@link #mCurFocusedWindowClient} differs
- * from {@link #mCurClient}.
- *
- * @see #mCurFocusedWindow
- */
- @Nullable
- EditorInfo mCurFocusedWindowEditorInfo;
-
- /**
* The {@link IRemoteInputConnection} last provided by the current client.
*/
IRemoteInputConnection mCurInputConnection;
@@ -1131,10 +1096,11 @@
mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
accessibilitySoftKeyboardSetting);
if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
} else if (isShowRequestedForCurrentWindow()) {
- showCurrentInputLocked(mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT,
+ showCurrentInputLocked(mImeBindingState.mFocusedWindow,
+ InputMethodManager.SHOW_IMPLICIT,
SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
}
} else if (stylusHandwritingEnabledUri.equals(uri)) {
@@ -1341,20 +1307,35 @@
@Override
public void onPackageDataCleared(String packageName, int uid) {
+ final int userId = getChangingUserId();
synchronized (ImfLock.class) {
+ final boolean isCurrentUser = (userId == mSettings.getUserId());
+ final AdditionalSubtypeMap additionalSubtypeMap;
+ final InputMethodSettings settings;
+ if (isCurrentUser) {
+ additionalSubtypeMap = mAdditionalSubtypeMap;
+ settings = mSettings;
+ } else {
+ additionalSubtypeMap = AdditionalSubtypeUtils.load(userId);
+ settings = queryInputMethodServicesInternal(mContext, userId,
+ additionalSubtypeMap, DirectBootAwareness.AUTO);
+ }
+
// Note that one package may implement multiple IMEs.
final ArrayList<String> changedImes = new ArrayList<>();
- for (InputMethodInfo imi : mSettings.getMethodList()) {
+ for (InputMethodInfo imi : settings.getMethodList()) {
if (imi.getPackageName().equals(packageName)) {
changedImes.add(imi.getId());
}
}
final AdditionalSubtypeMap newMap =
- mAdditionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
- if (newMap != mAdditionalSubtypeMap) {
- mAdditionalSubtypeMap = newMap;
+ additionalSubtypeMap.cloneWithRemoveOrSelf(changedImes);
+ if (newMap != additionalSubtypeMap) {
+ if (isCurrentUser) {
+ mAdditionalSubtypeMap = newMap;
+ }
AdditionalSubtypeUtils.save(
- mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId());
+ newMap, settings.getMethodMap(), settings.getUserId());
}
if (!changedImes.isEmpty()) {
mChangedPackages.add(packageName);
@@ -1629,7 +1610,7 @@
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SWITCH_USER);
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
@@ -1679,8 +1660,6 @@
final int userId = mActivityManagerInternal.getCurrentUserId();
- mLastSwitchUserId = userId;
-
// mSettings should be created before buildInputMethodListLocked
mSettings = InputMethodSettings.createEmptyMap(userId);
@@ -1704,6 +1683,7 @@
mClientController = new ClientController(mPackageManagerInternal);
synchronized (ImfLock.class) {
mClientController.addClientControllerCallback(c -> onClientRemoved(c));
+ mImeBindingState = ImeBindingState.newEmptyState();
}
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
@@ -1866,7 +1846,6 @@
+ " selectedIme=" + mSettings.getSelectedInputMethod());
}
- mLastSwitchUserId = newUserId;
if (mIsInteractive && clientToBeReset != null) {
final ClientState cs = mClientController.getClient(clientToBeReset.asBinder());
if (cs == null) {
@@ -2218,7 +2197,7 @@
clearClientSessionLocked(client);
clearClientSessionForAccessibilityLocked(client);
if (mCurClient == client) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
@@ -2232,9 +2211,8 @@
}
mBoundToAccessibility = false;
mCurClient = null;
- if (mCurFocusedWindowClient == client) {
- mCurFocusedWindowClient = null;
- mCurFocusedWindowEditorInfo = null;
+ if (mImeBindingState.mFocusedWindowClient == client) {
+ mImeBindingState = ImeBindingState.newEmptyState();
}
}
}
@@ -2283,7 +2261,7 @@
@GuardedBy("ImfLock.class")
void onUnbindCurrentMethodByReset() {
final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (winState != null && !winState.isRequestedImeVisible()
&& !mVisibilityStateComputer.isInputShown()) {
// Normally, the focus window will apply the IME visibility state to
@@ -2295,7 +2273,8 @@
// binding states in the first place.
final var statsToken = createStatsTokenForFocusedClient(false /* show */,
SoftInputShowHideReason.UNBIND_CURRENT_METHOD);
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken, STATE_HIDE_IME);
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
+ STATE_HIDE_IME);
}
}
@@ -2328,7 +2307,7 @@
@GuardedBy("ImfLock.class")
private boolean isShowRequestedForCurrentWindow() {
final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
return state != null && state.isRequestedImeVisible();
}
@@ -2346,10 +2325,9 @@
getCurTokenLocked(),
mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting,
UserHandle.getUserId(mCurClient.mUid),
- mCurClient.mSelfReportedDisplayId,
- mCurFocusedWindow, mCurEditorInfo, mCurFocusedWindowSoftInputMode,
- getSequenceNumberLocked());
- mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow);
+ mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
+ mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked());
+ mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
// Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
@@ -2376,7 +2354,7 @@
: createStatsTokenForFocusedClient(true /* show */,
SoftInputShowHideReason.ATTACH_NEW_INPUT);
mCurStatsToken = null;
- showCurrentInputLocked(mCurFocusedWindow, statsToken,
+ showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken,
mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
}
@@ -2450,7 +2428,7 @@
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (winState == null) {
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
@@ -2469,7 +2447,7 @@
}
if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
return InputBindResult.NO_IME;
}
@@ -3638,7 +3616,8 @@
Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken || mCurPerceptible == perceptible) {
+ if (mImeBindingState.mFocusedWindow != windowToken
+ || mCurPerceptible == perceptible) {
return;
}
mCurPerceptible = perceptible;
@@ -3732,7 +3711,7 @@
super.hideSoftInputFromServerForTest_enforcePermission();
synchronized (ImfLock.class) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
}
}
@@ -3907,7 +3886,8 @@
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
final boolean showForced = mVisibilityStateComputer.mShowForced;
- if (mCurFocusedWindow != windowToken && showForced && shouldClearFlag) {
+ if (mImeBindingState.mFocusedWindow != windowToken
+ && showForced && shouldClearFlag) {
mVisibilityStateComputer.mShowForced = false;
}
@@ -3925,7 +3905,7 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_INVALID_USER);
return InputBindResult.INVALID_USER;
}
@@ -3986,7 +3966,7 @@
+ " cs=" + cs);
}
- final boolean sameWindowFocused = mCurFocusedWindow == windowToken;
+ final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
(startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
@@ -4017,10 +3997,7 @@
null, null, null, null, -1, false);
}
- mCurFocusedWindow = windowToken;
- mCurFocusedWindowSoftInputMode = softInputMode;
- mCurFocusedWindowClient = cs;
- mCurFocusedWindowEditorInfo = editorInfo;
+ mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo);
mCurPerceptible = true;
// We want to start input before showing the IME, but after closing
@@ -4051,9 +4028,8 @@
break;
}
final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason());
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, statsToken,
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
imeVisRes.getState(), imeVisRes.getReason());
-
if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
// If focused display changed, we should unbind current method
// to make app window in previous display relayout after Ime
@@ -4092,7 +4068,7 @@
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
- if (!isImeClientFocused(mCurFocusedWindow, cs)) {
+ if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
@@ -4104,8 +4080,8 @@
@GuardedBy("ImfLock.class")
private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
final int uid = Binder.getCallingUid();
- if (mCurFocusedWindowClient != null && client != null
- && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
+ if (mImeBindingState.mFocusedWindowClient != null && client != null
+ && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
return true;
}
if (mSettings.getUserId() != UserHandle.getUserId(uid)) {
@@ -4728,12 +4704,11 @@
proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
proto.write(CUR_SEQ, getSequenceNumberLocked());
proto.write(CUR_CLIENT, Objects.toString(mCurClient));
- proto.write(CUR_FOCUSED_WINDOW_NAME,
- mWindowManagerInternal.getWindowName(mCurFocusedWindow));
+ mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
- proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE,
- InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
+ proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
+ mImeBindingState.mFocusedWindowSoftInputMode));
if (mCurEditorInfo != null) {
mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
@@ -4743,7 +4718,6 @@
proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId);
proto.write(SYSTEM_READY, mSystemReady);
- proto.write(LAST_SWITCH_USER_ID, mLastSwitchUserId);
proto.write(HAVE_CONNECTION, hasConnectionLocked());
proto.write(BOUND_TO_METHOD, mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
@@ -4855,12 +4829,12 @@
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
- show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
+ show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId);
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mCurFocusedWindowClient, mCurFocusedWindowEditorInfo, info.focusedWindowName,
- mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
- info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
- info.imeSurfaceParentName));
+ mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
+ info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
+ mInFullscreenMode, info.requestWindowName, info.imeControlTargetName,
+ info.imeLayerTargetName, info.imeSurfaceParentName));
if (statsToken != null) {
mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -5031,8 +5005,7 @@
case MSG_HIDE_ALL_INPUT_METHODS:
synchronized (ImfLock.class) {
@SoftInputShowHideReason final int reason = (int) msg.obj;
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */, reason);
-
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, reason);
}
return true;
case MSG_REMOVE_IME_SURFACE: {
@@ -5051,7 +5024,7 @@
IBinder windowToken = (IBinder) msg.obj;
synchronized (ImfLock.class) {
try {
- if (windowToken == mCurFocusedWindow
+ if (windowToken == mImeBindingState.mFocusedWindow
&& mEnabledSession != null && mEnabledSession.mSession != null) {
mEnabledSession.mSession.removeImeSurface();
}
@@ -5126,7 +5099,7 @@
case MSG_START_HANDWRITING:
synchronized (ImfLock.class) {
IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null || mCurFocusedWindow == null) {
+ if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
return true;
}
final HandwritingModeController.HandwritingSession session =
@@ -5134,7 +5107,7 @@
msg.arg1 /*requestId*/,
msg.arg2 /*pid*/,
mBindingController.getCurMethodUid(),
- mCurFocusedWindow);
+ mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
"Failed to start handwriting session for requestId: " + msg.arg1);
@@ -5187,11 +5160,11 @@
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
- mCurFocusedWindow, interactive);
+ mImeBindingState.mFocusedWindow, interactive);
if (imeVisRes != null) {
// Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
- mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */,
- imeVisRes.getState(), imeVisRes.getReason());
+ mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow,
+ null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason());
}
// Eligible IME processes use new "setInteractive" protocol.
mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
@@ -5881,7 +5854,7 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- if (mCurFocusedWindow != windowToken) {
+ if (mImeBindingState.mFocusedWindow != windowToken) {
// mCurPerceptible was set by the focused window, but it is no longer in
// control, so we reset mCurPerceptible.
mCurPerceptible = true;
@@ -5895,7 +5868,7 @@
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
// the next focused window starting the input connection.
- if (mLastImeTargetWindow != mCurFocusedWindow) {
+ if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) {
mMenuController.hideInputMethodMenuLocked();
}
}
@@ -6189,11 +6162,7 @@
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked());
p.println(" mCurPerceptible=" + mCurPerceptible);
- p.println(" mCurFocusedWindow=" + mCurFocusedWindow
- + " softInputMode="
- + InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode)
- + " client=" + mCurFocusedWindowClient);
- focusedWindowClient = mCurFocusedWindowClient;
+ mImeBindingState.dump(" ", p);
p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked()
+ " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+ mBindingController.isVisibleBound());
@@ -6244,7 +6213,8 @@
p.println("No input method client.");
}
- if (focusedWindowClient != null && client != focusedWindowClient) {
+ if (mImeBindingState.mFocusedWindowClient != null
+ && client != mImeBindingState.mFocusedWindowClient) {
p.println(" ");
p.println("Warning: Current input method client doesn't match the last focused. "
+ "window.");
@@ -6252,7 +6222,8 @@
p.println(" ");
pw.flush();
try {
- TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args);
+ TransferPipe.dumpAsync(
+ mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args);
} catch (IOException | RemoteException e) {
p.println("Failed to dump input method client in focused window: " + e);
}
@@ -6324,8 +6295,6 @@
@ShellCommandResult
private int onCommandWithSystemIdentity(@Nullable String cmd) {
switch (TextUtils.emptyIfNull(cmd)) {
- case "get-last-switch-user-id":
- return mService.getLastSwitchUserId(this);
case "tracing":
return mService.handleShellCommandTraceInputMethod(this);
case "ime": { // For "adb shell ime <command>".
@@ -6439,15 +6408,6 @@
// ----------------------------------------------------------------------
// Shell command handlers:
- @BinderThread
- @ShellCommandResult
- private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) {
- synchronized (ImfLock.class) {
- shellCommand.getOutPrintWriter().println(mLastSwitchUserId);
- return ShellCommandResult.SUCCESS;
- }
- }
-
/**
* Handles {@code adb shell ime list}.
* @param shellCommand {@link ShellCommand} object that is handling this command.
@@ -6698,7 +6658,7 @@
final String nextIme;
final List<InputMethodInfo> nextEnabledImes;
if (userId == mSettings.getUserId()) {
- hideCurrentInputLocked(mCurFocusedWindow, 0 /* flags */,
+ hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
mBindingController.unbindCurrentMethod();
@@ -6832,11 +6792,11 @@
@NonNull
private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
@SoftInputShowHideReason int reason) {
- final int uid = mCurFocusedWindowClient != null
- ? mCurFocusedWindowClient.mUid
+ final int uid = mImeBindingState.mFocusedWindowClient != null
+ ? mImeBindingState.mFocusedWindowClient.mUid
: -1;
- final var packageName = mCurFocusedWindowEditorInfo != null
- ? mCurFocusedWindowEditorInfo.packageName
+ final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null
+ ? mImeBindingState.mFocusedWindowEditorInfo.packageName
: "uid(" + uid + ")";
return ImeTracker.forLogging().onStart(packageName, uid,
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fc99471..0ca4808 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -449,7 +449,7 @@
}
/**
- * Sends a reliable message rom this client to a nanoapp.
+ * Sends a reliable message from this client to a nanoapp.
*
* @param message the message to send
* @param transactionCallback The callback to use to confirm the delivery of the message for
@@ -473,6 +473,12 @@
@Nullable IContextHubTransactionCallback transactionCallback) {
ContextHubServiceUtil.checkPermissions(mContext);
+ // Clear the isReliable and messageSequenceNumber fields.
+ // These will be set to true and a real value if the message
+ // is reliable.
+ message.setIsReliable(false);
+ message.setMessageSequenceNumber(0);
+
@ContextHubTransaction.Result int result;
if (isRegistered()) {
int authState = mMessageChannelNanoappIdMap.getOrDefault(
@@ -485,7 +491,9 @@
// Return a bland error code for apps targeting old SDKs since they wouldn't be able
// to use an error code added in S.
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
- } else if (authState == AUTHORIZATION_UNKNOWN) {
+ }
+
+ if (authState == AUTHORIZATION_UNKNOWN) {
// Only check permissions the first time a nanoapp is queried since nanoapp
// permissions don't currently change at runtime. If the host permission changes
// later, that'll be checked by onOpChanged.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 29ea071..c80f988 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -814,7 +814,8 @@
// storage is locked, instead of when the user is stopped. This would ensure the flags get
// reset if CE storage is locked later for a user that allows delayed locking.
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
UserProperties userProperties = mUserManager.getUserProperties(UserHandle.of(userId));
if (userProperties != null && userProperties.getAllowStoppingUserWithDelayedLocking()) {
return;
diff --git a/services/core/java/com/android/server/net/TEST_MAPPING b/services/core/java/com/android/server/net/TEST_MAPPING
index e0376ed..4fc1a17 100644
--- a/services/core/java/com/android/server/net/TEST_MAPPING
+++ b/services/core/java/com/android/server/net/TEST_MAPPING
@@ -27,6 +27,15 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "FrameworksVpnTests",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ],
+ "file_patterns": ["VpnManagerService\\.java"]
}
]
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index f645eaa..3ecc58e 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -942,6 +942,23 @@
return false;
}
+ protected boolean isPackageOrComponentAllowedWithPermission(ComponentName component,
+ int userId) {
+ if (!(isPackageOrComponentAllowed(component.flattenToString(), userId)
+ || isPackageOrComponentAllowed(component.getPackageName(), userId))) {
+ return false;
+ }
+ return componentHasBindPermission(component, userId);
+ }
+
+ private boolean componentHasBindPermission(ComponentName component, int userId) {
+ ServiceInfo info = getServiceInfo(component, userId);
+ if (info == null) {
+ return false;
+ }
+ return mConfig.bindPermission.equals(info.permission);
+ }
+
boolean isPackageOrComponentUserSet(String pkgOrComponent, int userId) {
synchronized (mApproved) {
ArraySet<String> services = mUserSetServices.get(userId);
@@ -1003,6 +1020,7 @@
for (int uid : uidList) {
if (isPackageAllowed(pkgName, UserHandle.getUserId(uid))) {
anyServicesInvolved = true;
+ trimApprovedListsForInvalidServices(pkgName, UserHandle.getUserId(uid));
}
}
}
@@ -1135,8 +1153,7 @@
synchronized (mMutex) {
if (enabled) {
- if (isPackageOrComponentAllowed(component.flattenToString(), userId)
- || isPackageOrComponentAllowed(component.getPackageName(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(component, userId)) {
registerServiceLocked(component, userId);
} else {
Slog.d(TAG, component + " no longer has permission to be bound");
@@ -1270,6 +1287,33 @@
return removed;
}
+ private void trimApprovedListsForInvalidServices(String packageName, int userId) {
+ synchronized (mApproved) {
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ return;
+ }
+ for (int i = 0; i < approvedByType.size(); i++) {
+ final ArraySet<String> approved = approvedByType.valueAt(i);
+ for (int j = approved.size() - 1; j >= 0; j--) {
+ final String approvedPackageOrComponent = approved.valueAt(j);
+ if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
+ final ComponentName component = ComponentName.unflattenFromString(
+ approvedPackageOrComponent);
+ if (component != null && !componentHasBindPermission(component, userId)) {
+ approved.removeAt(j);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no bind permission found "
+ + mConfig.bindPermission);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
protected String getPackageName(String packageOrComponent) {
final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
if (component != null) {
@@ -1519,8 +1563,7 @@
void reregisterService(final ComponentName cn, final int userId) {
// If rebinding a package that died, ensure it still has permission
// after the rebind delay
- if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
- || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+ if (isPackageOrComponentAllowedWithPermission(cn, userId)) {
registerService(cn, userId);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 3a7ac0b..b98424c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1204,6 +1204,10 @@
}
}
+ private static boolean privateSpaceFlagsEnabled() {
+ return allowPrivateProfile() && android.multiuser.Flags.enablePrivateSpaceFeatures();
+ }
+
private final class SavePolicyFileRunnable implements Runnable {
@Override
public void run() {
@@ -2142,7 +2146,7 @@
}
private boolean isProfileUnavailable(String action) {
- return allowPrivateProfile() ?
+ return privateSpaceFlagsEnabled() ?
action.equals(Intent.ACTION_PROFILE_UNAVAILABLE) :
action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
}
@@ -2744,7 +2748,7 @@
filter.addAction(Intent.ACTION_USER_REMOVED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- if (allowPrivateProfile()){
+ if (privateSpaceFlagsEnabled()){
filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE);
}
getContext().registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, filter, null, null);
@@ -12050,11 +12054,12 @@
@Override
public void onServiceAdded(ManagedServiceInfo info) {
if (lifetimeExtensionRefactor()) {
- // Generally, only System or System UI should have the permissions to call
- // registerSystemService.
- // isCallerSystemorPhone tells us whether the caller is System. Then, if it's not
- // the system, we know it's system UI.
- info.isSystemUi = !isCallerSystemOrPhone();
+ // We explicitly check the status bar permission for the uid in the info object.
+ // We can't use the calling uid here because it's probably always system server.
+ // Note that this will also be true for the shell.
+ info.isSystemUi = getContext().checkPermission(
+ android.Manifest.permission.STATUS_BAR_SERVICE, -1, info.uid)
+ == PERMISSION_GRANTED;
}
final INotificationListener listener = (INotificationListener) info.service;
final NotificationRankingUpdate update;
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
deleted file mode 100644
index 37b263c..0000000
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.notification;
-
-import android.app.Flags;
-import android.app.NotificationManager.Policy;
-import android.service.notification.ZenModeConfig;
-import android.service.notification.ZenPolicy;
-
-/**
- * Converters between different Zen representations.
- */
-class ZenAdapters {
-
- static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
- ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
- .allowAlarms(policy.allowAlarms())
- .allowCalls(
- policy.allowCalls()
- ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowConversations(
- policy.allowConversations()
- ? notificationPolicyConversationSendersToZenPolicy(
- policy.allowConversationsFrom())
- : ZenPolicy.CONVERSATION_SENDERS_NONE)
- .allowEvents(policy.allowEvents())
- .allowMedia(policy.allowMedia())
- .allowMessages(
- policy.allowMessages()
- ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
- : ZenPolicy.PEOPLE_TYPE_NONE)
- .allowReminders(policy.allowReminders())
- .allowRepeatCallers(policy.allowRepeatCallers())
- .allowSystem(policy.allowSystem());
-
- if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
- zenPolicyBuilder.showBadges(policy.showBadges())
- .showFullScreenIntent(policy.showFullScreenIntents())
- .showInAmbientDisplay(policy.showAmbient())
- .showInNotificationList(policy.showInNotificationList())
- .showLights(policy.showLights())
- .showPeeking(policy.showPeeking())
- .showStatusBarIcons(policy.showStatusBarIcons());
- }
-
- if (Flags.modesApi()) {
- zenPolicyBuilder.allowPriorityChannels(policy.allowPriorityChannels());
- }
-
- return zenPolicyBuilder.build();
- }
-
- @ZenPolicy.ConversationSenders
- private static int notificationPolicyConversationSendersToZenPolicy(
- int npPriorityConversationSenders) {
- switch (npPriorityConversationSenders) {
- case Policy.CONVERSATION_SENDERS_ANYONE:
- return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
- case Policy.CONVERSATION_SENDERS_IMPORTANT:
- return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
- case Policy.CONVERSATION_SENDERS_NONE:
- return ZenPolicy.CONVERSATION_SENDERS_NONE;
- case Policy.CONVERSATION_SENDERS_UNSET:
- default:
- return ZenPolicy.CONVERSATION_SENDERS_UNSET;
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/ZenModeEventLogger.java b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
index b9a267f..8e37b4f 100644
--- a/services/core/java/com/android/server/notification/ZenModeEventLogger.java
+++ b/services/core/java/com/android/server/notification/ZenModeEventLogger.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager;
import android.os.Process;
import android.service.notification.DNDPolicyProto;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
import android.service.notification.ZenModeConfig.ZenRule;
@@ -591,9 +592,11 @@
// This applies to both call and message senders, but not conversation senders,
// where they use the same enum values.
proto.write(DNDPolicyProto.ALLOW_CALLS_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowCallsFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowCallsFrom()));
proto.write(DNDPolicyProto.ALLOW_MESSAGES_FROM,
- ZenModeConfig.getZenPolicySenders(mNewPolicy.allowMessagesFrom()));
+ ZenAdapters.notificationPolicySendersToZenPolicyPeopleType(
+ mNewPolicy.allowMessagesFrom()));
proto.write(DNDPolicyProto.ALLOW_CONVERSATIONS_FROM,
mNewPolicy.allowConversationsFrom());
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 6857869..289faf4 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -35,6 +35,8 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -84,6 +86,7 @@
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
@@ -535,36 +538,40 @@
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
requirePublicOrigin("updateAutomaticZenRule", origin);
- ZenModeConfig newConfig;
+ if (ruleId == null) {
+ throw new IllegalArgumentException("ruleId cannot be null");
+ }
synchronized (mConfigLock) {
if (mConfig == null) return false;
if (DEBUG) {
Log.d(TAG, "updateAutomaticZenRule zenRule=" + automaticZenRule
+ " reason=" + reason);
}
- newConfig = mConfig.copy();
- ZenModeConfig.ZenRule rule;
- if (ruleId == null) {
- throw new IllegalArgumentException("Rule doesn't exist");
- } else {
- rule = newConfig.automaticRules.get(ruleId);
- if (rule == null || !canManageAutomaticZenRule(rule)) {
- throw new SecurityException(
- "Cannot update rules not owned by your condition provider");
- }
+ ZenModeConfig.ZenRule oldRule = mConfig.automaticRules.get(ruleId);
+ if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+ throw new SecurityException(
+ "Cannot update rules not owned by your condition provider");
}
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenModeConfig.ZenRule newRule = requireNonNull(newConfig.automaticRules.get(ruleId));
if (!Flags.modesApi()) {
- if (rule.enabled != automaticZenRule.isEnabled()) {
- dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
+ if (newRule.enabled != automaticZenRule.isEnabled()) {
+ dispatchOnAutomaticRuleStatusChanged(mConfig.user, newRule.getPkg(), ruleId,
automaticZenRule.isEnabled()
? AUTOMATIC_RULE_STATUS_ENABLED
: AUTOMATIC_RULE_STATUS_DISABLED);
}
}
- populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false);
+ boolean updated = populateZenRule(newRule.pkg, automaticZenRule, newRule,
+ origin, /* isNew= */ false);
+ if (Flags.modesApi() && !updated) {
+ // Bail out so we don't have the side effects of updating a rule (i.e. dropping
+ // condition) when no changes happen.
+ return true;
+ }
return setConfigLocked(newConfig, origin, reason,
- rule.component, true, callingUid);
+ newRule.component, true, callingUid);
}
}
@@ -1072,31 +1079,67 @@
return null;
}
- private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ /**
+ * Populates a {@code ZenRule} with the content of the {@link AutomaticZenRule}. Can be used for
+ * both rule creation or update (distinguished by the {@code isNew} parameter. The change is
+ * applied differently depending on the origin; for example app-provided changes might be
+ * ignored (if the rule was previously customized by the user), while user-provided changes
+ * update the user-modified bitmasks for any modifications.
+ *
+ * <p>Returns {@code true} if the rule was modified. Note that this is not equivalent to
+ * {@link ZenRule#equals} or {@link AutomaticZenRule#equals}, for various reasons:
+ * <ul>
+ * <li>some metadata-related fields are not considered
+ * <li>some fields (like {@code condition} are always reset, and ignored for this result
+ * <li>an app may provide changes that are not actually applied, as described above
+ * </ul>
+ */
+ private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule,
@ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
+ boolean modified = false;
// These values can always be edited by the app, so we apply changes immediately.
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = mClock.millis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
+ modified = true;
}
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- if (rule.enabled != automaticZenRule.isEnabled()) {
- rule.snoozing = false;
+ if (!Objects.equals(rule.conditionId, azr.getConditionId())) {
+ rule.conditionId = azr.getConditionId();
+ modified = true;
}
- rule.enabled = automaticZenRule.isEnabled();
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
- rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
- rule.iconResName =
- drawableResIdToResName(rule.pkg, automaticZenRule.getIconResId());
- rule.triggerDescription = automaticZenRule.getTriggerDescription();
- rule.type = automaticZenRule.getType();
+ if (rule.enabled != azr.isEnabled()) {
+ rule.enabled = azr.isEnabled();
+ rule.snoozing = false;
+ modified = true;
+ }
+ if (!Objects.equals(rule.configurationActivity, azr.getConfigurationActivity())) {
+ rule.configurationActivity = azr.getConfigurationActivity();
+ modified = true;
+ }
+ if (rule.allowManualInvocation != azr.isManualInvocationAllowed()) {
+ rule.allowManualInvocation = azr.isManualInvocationAllowed();
+ modified = true;
+ }
+ String iconResName = drawableResIdToResName(rule.pkg, azr.getIconResId());
+ if (!Objects.equals(rule.iconResName, iconResName)) {
+ rule.iconResName = iconResName;
+ modified = true;
+ }
+ if (!Objects.equals(rule.triggerDescription, azr.getTriggerDescription())) {
+ rule.triggerDescription = azr.getTriggerDescription();
+ modified = true;
+ }
+ if (rule.type != azr.getType()) {
+ rule.type = azr.getType();
+ modified = true;
+ }
// TODO: b/310620812 - Remove this once FLAG_MODES_API is inlined.
- rule.modified = automaticZenRule.isModified();
+ rule.modified = azr.isModified();
// Name is treated differently than other values:
// App is allowed to update name if the name was not modified by the user (even if
@@ -1106,7 +1149,8 @@
String previousName = rule.name;
if (isNew || doesOriginAlwaysUpdateValues(origin)
|| (rule.userModifiedFields & AutomaticZenRule.FIELD_NAME) == 0) {
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
+ modified |= !Objects.equals(rule.name, previousName);
}
// For the remaining values, rules can always have all values updated if:
@@ -1118,50 +1162,56 @@
// For all other values, if updates are not allowed, we discard the update.
if (!updateValues) {
- return;
+ return modified;
}
// Updates the bitmasks if the origin of the change is the user.
boolean updateBitmask = (origin == UPDATE_ORIGIN_USER);
- if (updateBitmask && !TextUtils.equals(previousName, automaticZenRule.getName())) {
+ if (updateBitmask && !TextUtils.equals(previousName, azr.getName())) {
rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME;
}
int newZenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- if (updateBitmask && rule.zenMode != newZenMode) {
- rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ if (rule.zenMode != newZenMode) {
+ rule.zenMode = newZenMode;
+ if (updateBitmask) {
+ rule.userModifiedFields |= AutomaticZenRule.FIELD_INTERRUPTION_FILTER;
+ }
+ modified = true;
}
- // Updates the values in the ZenRule itself.
- rule.zenMode = newZenMode;
-
// Updates the bitmask and values for all policy fields, based on the origin.
- updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew);
+ modified |= updatePolicy(rule, azr.getZenPolicy(), updateBitmask, isNew);
// Updates the bitmask and values for all device effect fields, based on the origin.
- updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
+ modified |= updateZenDeviceEffects(rule, azr.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
+
+ return modified;
} else {
- if (rule.enabled != automaticZenRule.isEnabled()) {
+ if (rule.enabled != azr.isEnabled()) {
rule.snoozing = false;
}
- rule.name = automaticZenRule.getName();
+ rule.name = azr.getName();
rule.condition = null;
- rule.conditionId = automaticZenRule.getConditionId();
- rule.enabled = automaticZenRule.isEnabled();
- rule.modified = automaticZenRule.isModified();
- rule.zenPolicy = automaticZenRule.getZenPolicy();
+ rule.conditionId = azr.getConditionId();
+ rule.enabled = azr.isEnabled();
+ rule.modified = azr.isModified();
+ rule.zenPolicy = azr.getZenPolicy();
rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
- automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
- rule.configurationActivity = automaticZenRule.getConfigurationActivity();
+ azr.getInterruptionFilter(), Global.ZEN_MODE_OFF);
+ rule.configurationActivity = azr.getConfigurationActivity();
if (isNew) {
rule.id = ZenModeConfig.newRuleId();
rule.creationTime = System.currentTimeMillis();
- rule.component = automaticZenRule.getOwner();
+ rule.component = azr.getOwner();
rule.pkg = pkg;
}
+
+ // Only the MODES_API path cares about the result, so just return whatever here.
+ return true;
}
}
@@ -1181,16 +1231,19 @@
* provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy}
* for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to
* reflect the changes being applied (if applicable, i.e. if the update is from the user).
+ *
+ * <p>Returns {@code true} if the policy of the rule was modified.
*/
- private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+ private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
boolean updateBitmask, boolean isNew) {
if (newPolicy == null) {
if (isNew) {
// Newly created rule with no provided policy; fill in with the default.
zenRule.zenPolicy = mDefaultConfig.toZenPolicy();
+ return true;
}
// Otherwise, a null policy means no policy changes, so we can stop here.
- return;
+ return false;
}
// If oldPolicy is null, we compare against the default policy when determining which
@@ -1271,6 +1324,8 @@
}
zenRule.zenPolicyUserModifiedFields = userModifiedFields;
}
+
+ return !newPolicy.equals(oldPolicy);
}
/**
@@ -1282,12 +1337,14 @@
* <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
* treated especially: for a new rule, they are blanked out; for an updated rule, previous
* values are preserved.
+ *
+ * <p>Returns {@code true} if the device effects of the rule were modified.
*/
- private static void updateZenDeviceEffects(ZenRule zenRule,
+ private static boolean updateZenDeviceEffects(ZenRule zenRule,
@Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
// Same as with ZenPolicy, supplying null effects means keeping the previous ones.
if (newEffects == null) {
- return;
+ return false;
}
ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
@@ -1348,6 +1405,8 @@
}
zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
+
+ return !newEffects.equals(oldEffects);
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -2504,7 +2563,7 @@
if (resId == 0) {
return null;
}
- Objects.requireNonNull(packageName);
+ requireNonNull(packageName);
try {
final Resources res = mPm.getResourcesForApplication(packageName);
return res.getResourceName(resId);
diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
index 0abe50f..71800ef 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
@@ -32,12 +32,13 @@
import android.app.ondeviceintelligence.IProcessingSignal;
import android.app.ondeviceintelligence.IResponseCallback;
import android.app.ondeviceintelligence.IStreamingResponseCallback;
-import android.app.ondeviceintelligence.ITokenCountCallback;
+import android.app.ondeviceintelligence.ITokenInfoCallback;
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
+import android.os.Binder;
import android.os.Bundle;
import android.os.ICancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -47,11 +48,12 @@
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.ondeviceintelligence.IOnDeviceIntelligenceService;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
import android.service.ondeviceintelligence.IRemoteProcessingService;
import android.service.ondeviceintelligence.IRemoteStorageService;
import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback;
import android.service.ondeviceintelligence.OnDeviceIntelligenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import android.text.TextUtils;
import android.util.Slog;
@@ -69,7 +71,7 @@
* This is the system service for handling calls on the
* {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. This
* service holds connection references to the underlying remote services i.e. the isolated service
- * {@link OnDeviceTrustedInferenceService} and a regular
+ * {@link OnDeviceSandboxedInferenceService} and a regular
* service counter part {@link OnDeviceIntelligenceService}.
*
* Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of
@@ -90,7 +92,7 @@
protected final Object mLock = new Object();
- private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService;
+ private RemoteOnDeviceSandboxedInferenceService mRemoteInferenceService;
private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService;
volatile boolean mIsServiceEnabled;
@@ -165,7 +167,7 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeature(id, featureCallback));
+ service -> service.getFeature(Binder.getCallingUid(), id, featureCallback));
}
@Override
@@ -185,7 +187,7 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.listFeatures(listFeaturesCallback));
+ service -> service.listFeatures(Binder.getCallingUid(), listFeaturesCallback));
}
@Override
@@ -207,7 +209,8 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.getFeatureDetails(feature, featureDetailsCallback));
+ service -> service.getFeatureDetails(Binder.getCallingUid(), feature,
+ featureDetailsCallback));
}
@Override
@@ -227,33 +230,35 @@
}
ensureRemoteIntelligenceServiceInitialized();
mRemoteOnDeviceIntelligenceService.post(
- service -> service.requestFeatureDownload(feature, cancellationSignal,
+ service -> service.requestFeatureDownload(Binder.getCallingUid(), feature,
+ cancellationSignal,
downloadCallback));
}
@Override
- public void requestTokenCount(Feature feature,
+ public void requestTokenInfo(Feature feature,
Content request, ICancellationSignal cancellationSignal,
- ITokenCountCallback tokenCountcallback) throws RemoteException {
+ ITokenInfoCallback tokenInfoCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing");
Objects.requireNonNull(feature);
Objects.requireNonNull(request);
- Objects.requireNonNull(tokenCountcallback);
+ Objects.requireNonNull(tokenInfoCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
Slog.w(TAG, "Service not available");
- tokenCountcallback.onFailure(
+ tokenInfoCallback.onFailure(
OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE,
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.requestTokenCount(feature, request, cancellationSignal,
- tokenCountcallback));
+ service -> service.requestTokenInfo(Binder.getCallingUid(), feature, request,
+ cancellationSignal,
+ tokenInfoCallback));
}
@Override
@@ -267,7 +272,6 @@
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest");
Objects.requireNonNull(feature);
Objects.requireNonNull(responseCallback);
- Objects.requireNonNull(request);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
if (!mIsServiceEnabled) {
@@ -277,9 +281,10 @@
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequest(feature, request, requestType,
+ service -> service.processRequest(Binder.getCallingUid(), feature, request,
+ requestType,
cancellationSignal, processingSignal,
responseCallback));
}
@@ -293,7 +298,6 @@
IStreamingResponseCallback streamingCallback) throws RemoteException {
Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming");
Objects.requireNonNull(feature);
- Objects.requireNonNull(request);
Objects.requireNonNull(streamingCallback);
mContext.enforceCallingOrSelfPermission(
Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG);
@@ -304,9 +308,10 @@
"OnDeviceIntelligenceManagerService is unavailable",
new PersistableBundle());
}
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
- service -> service.processRequestStreaming(feature, request, requestType,
+ service -> service.processRequestStreaming(Binder.getCallingUid(), feature,
+ request, requestType,
cancellationSignal, processingSignal,
streamingCallback));
}
@@ -346,7 +351,7 @@
Bundle processingState,
IProcessingUpdateStatusCallback callback) {
try {
- ensureRemoteTrustedInferenceServiceInitialized();
+ ensureRemoteInferenceServiceInitialized();
mRemoteInferenceService.post(
service -> service.updateProcessingState(
processingState, callback));
@@ -363,22 +368,24 @@
};
}
- private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException {
+ private void ensureRemoteInferenceServiceInitialized() throws RemoteException {
synchronized (mLock) {
if (mRemoteInferenceService == null) {
String serviceName = mContext.getResources().getString(
- R.string.config_defaultOnDeviceTrustedInferenceService);
+ R.string.config_defaultOnDeviceSandboxedInferenceService);
validateService(serviceName, true);
- mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext,
+ mRemoteInferenceService = new RemoteOnDeviceSandboxedInferenceService(mContext,
ComponentName.unflattenFromString(serviceName),
UserHandle.SYSTEM.getIdentifier());
mRemoteInferenceService.setServiceLifecycleCallbacks(
new ServiceConnector.ServiceLifecycleCallbacks<>() {
@Override
public void onConnected(
- @NonNull IOnDeviceTrustedInferenceService service) {
+ @NonNull IOnDeviceSandboxedInferenceService service) {
try {
ensureRemoteIntelligenceServiceInitialized();
+ mRemoteOnDeviceIntelligenceService.post(
+ intelligenceService -> intelligenceService.notifyInferenceServiceConnected());
service.registerRemoteStorageService(
getIRemoteStorageService());
} catch (RemoteException ex) {
@@ -433,7 +440,7 @@
}
checkServiceRequiresPermission(serviceInfo,
- Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE);
+ Manifest.permission.BIND_ON_DEVICE_SANDBOXED_INFERENCE_SERVICE);
if (!isIsolatedService(serviceInfo)) {
throw new SecurityException(
"Call required an isolated service, but the configured service: "
diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
similarity index 73%
rename from services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
rename to services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
index cc8e788..69ba1d2 100644
--- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java
+++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java
@@ -22,18 +22,18 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService;
-import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService;
+import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService;
+import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService;
import com.android.internal.infra.ServiceConnector;
/**
- * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding
+ * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding
* logic set by the service implementation via a SecureSettings flag.
*/
-public class RemoteOnDeviceTrustedInferenceService extends
- ServiceConnector.Impl<IOnDeviceTrustedInferenceService> {
+public class RemoteOnDeviceSandboxedInferenceService extends
+ ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> {
/**
* Creates an instance of {@link ServiceConnector}
*
@@ -43,12 +43,12 @@
* {@link Context#unbindService unbinding}
* @param userId to be used for {@link Context#bindServiceAsUser binding}
*/
- RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName,
+ RemoteOnDeviceSandboxedInferenceService(Context context, ComponentName serviceName,
int userId) {
super(context, new Intent(
- OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
+ OnDeviceSandboxedInferenceService.SERVICE_INTERFACE).setComponent(serviceName),
BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
- IOnDeviceTrustedInferenceService.Stub::asInterface);
+ IOnDeviceSandboxedInferenceService.Stub::asInterface);
// Bind right away
connect();
diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 4f86adf..4eb8b2b 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -354,6 +354,10 @@
DevicePolicyManager getDevicePolicyManager() {
return mContext.getSystemService(DevicePolicyManager.class);
}
+
+ void setSystemProperty(String key, String value) {
+ SystemProperties.set(key, value);
+ }
}
BugreportManagerServiceImpl(Context context) {
@@ -737,7 +741,7 @@
@GuardedBy("mLock")
private IDumpstate startAndGetDumpstateBinderServiceLocked() {
// Start bugreport service.
- SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.start", BUGREPORT_SERVICE);
IDumpstate ds = null;
boolean timedOut = false;
@@ -769,7 +773,7 @@
// This tells init to cancel bugreportd service. Note that this is achieved through
// setting a system property which is not thread-safe. So the lock here offers
// thread-safety only among callers of the API.
- SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
+ mInjector.setSystemProperty("ctl.stop", BUGREPORT_SERVICE);
}
@RequiresPermission(android.Manifest.permission.DUMP)
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 23d48e8..9af2b3f 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -389,7 +389,8 @@
*/
boolean canLauncherAccessProfile(ComponentName launcherComponent, int userId) {
if (android.os.Flags.allowPrivateProfile()
- && Flags.enablePermissionToAccessHiddenProfiles()) {
+ && Flags.enablePermissionToAccessHiddenProfiles()
+ && Flags.enablePrivateSpaceFeatures()) {
if (mUmInternal.getUserProperties(userId).getProfileApiVisibility()
!= UserProperties.PROFILE_API_VISIBILITY_HIDDEN) {
return true;
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 9afdde5..b5476fd 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -760,13 +760,18 @@
if (pkgName == null) {
if (!mCrossProfileIntentResolverEngine.shouldSkipCurrentProfile(this, intent,
resolvedType, userId)) {
- /*
- Check for results in the current profile only if there is no
- {@link CrossProfileIntentFilter} for user with flag
- {@link PackageManager.SKIP_CURRENT_PROFILE} set.
- */
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
- intent, resolvedType, flags, userId), userId));
+
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
+ intent, resolvedType, flags, userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ /*
+ Check for results in the current profile only if there is no
+ {@link CrossProfileIntentFilter} for user with flag
+ {@link PackageManager.SKIP_CURRENT_PROFILE} set.
+ */
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
addInstant = isInstantAppResolutionAllowed(intent, result, userId,
false /*skipPackageCheck*/, flags);
@@ -788,9 +793,13 @@
if (setting != null && setting.getAndroidPackage() != null && (resolveForStart
|| !shouldFilterApplication(setting, filterCallingUid, userId))) {
- result.addAll(filterIfNotSystemUser(mComponentResolver.queryActivities(this,
+ final List<ResolveInfo> queryResult = mComponentResolver.queryActivities(this,
intent, resolvedType, flags, setting.getAndroidPackage().getActivities(),
- userId), userId));
+ userId);
+ // If the user doesn't exist, the queryResult is null
+ if (queryResult != null) {
+ result.addAll(filterIfNotSystemUser(queryResult, userId));
+ }
}
if (result == null || result.size() == 0) {
// the caller wants to resolve for a particular package; however, there
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 3abf3a5..ecfc768 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -50,13 +50,17 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApexStagedEvent;
import android.content.pm.Flags;
+import android.content.pm.IPackageManagerNative;
+import android.content.pm.IStagedApexObserver;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.dex.ArtManager;
import android.os.Binder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
@@ -1054,6 +1058,10 @@
artManager.scheduleBackgroundDexoptJob();
}
}, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+ if (Flags.useArtServiceV2()) {
+ StagedApexObserver.registerForStagedApexUpdates(artManager);
+ }
}
/**
@@ -1168,4 +1176,32 @@
&& dexoptOptions.isCompilationEnabled()
&& !isApex;
}
+
+ private static class StagedApexObserver extends IStagedApexObserver.Stub {
+ private final @NonNull ArtManagerLocal mArtManager;
+
+ static void registerForStagedApexUpdates(@NonNull ArtManagerLocal artManager) {
+ IPackageManagerNative packageNative = IPackageManagerNative.Stub.asInterface(
+ ServiceManager.getService("package_native"));
+ if (packageNative == null) {
+ Log.e(TAG, "No IPackageManagerNative");
+ return;
+ }
+
+ try {
+ packageNative.registerStagedApexObserver(new StagedApexObserver(artManager));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register staged apex observer", e);
+ }
+ }
+
+ private StagedApexObserver(@NonNull ArtManagerLocal artManager) {
+ mArtManager = artManager;
+ }
+
+ @Override
+ public void onApexStaged(@NonNull ApexStagedEvent event) {
+ mArtManager.onApexStaged(event.stagedApexModuleNames);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 186cf5e..4bfd077 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1080,7 +1080,7 @@
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
requests, Collections.unmodifiableMap(mPm.mPackages),
versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mPm.mInjector.getSystemConfig());
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.setError("Reconciliation failed...", e);
@@ -3810,7 +3810,7 @@
mPm.mPackages, Collections.singletonMap(pkgName,
mPm.getSettingsVersionForPackage(parsedPackage)),
mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mPm.mInjector.getSystemConfig());
if ((scanFlags & SCAN_AS_APEX) == 0) {
appIdCreated = optimisticallyRegisterAppId(installRequest);
} else {
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 6b56b85..c7ebb3c 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -584,7 +584,8 @@
return android.os.Flags.allowPrivateProfile()
&& Flags.enableHidingProfiles()
&& Flags.enableLauncherAppsHiddenProfileChecks()
- && Flags.enablePermissionToAccessHiddenProfiles();
+ && Flags.enablePermissionToAccessHiddenProfiles()
+ && Flags.enablePrivateSpaceFeatures();
}
@VisibleForTesting // We override it in unit tests
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index ef8453d..29de26e 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,6 +31,7 @@
import static android.content.pm.PackageManager.DELETE_ALL_USERS;
import static android.content.pm.PackageManager.DELETE_ARCHIVE;
import static android.content.pm.PackageManager.DELETE_KEEP_DATA;
+import static android.content.pm.PackageManager.INSTALL_UNARCHIVE;
import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
import static android.os.PowerExemptionManager.REASON_PACKAGE_UNARCHIVE;
@@ -754,8 +755,9 @@
int draftSessionId;
try {
- draftSessionId = Binder.withCleanCallingIdentity(() ->
- createDraftSession(packageName, installerPackage, statusReceiver, userId));
+ draftSessionId = Binder.withCleanCallingIdentity(
+ () -> createDraftSession(packageName, installerPackage, callerPackageName,
+ statusReceiver, userId));
} catch (RuntimeException e) {
if (e.getCause() instanceof IOException) {
throw ExceptionUtils.wrap((IOException) e.getCause());
@@ -795,11 +797,18 @@
}
private int createDraftSession(String packageName, String installerPackage,
+ String callerPackageName,
IntentSender statusReceiver, int userId) throws IOException {
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.setAppPackageName(packageName);
- sessionParams.installFlags = INSTALL_UNARCHIVE_DRAFT;
+ sessionParams.setAppLabel(
+ mContext.getString(com.android.internal.R.string.unarchival_session_app_label));
+ sessionParams.setAppIcon(
+ getArchivedAppIcon(packageName, UserHandle.of(userId), callerPackageName));
+ // To make sure SessionInfo::isUnarchival returns true for draft sessions,
+ // INSTALL_UNARCHIVE is also set.
+ sessionParams.installFlags = (INSTALL_UNARCHIVE_DRAFT | INSTALL_UNARCHIVE);
int installerUid = mPm.snapshotComputer().getPackageUid(installerPackage, 0, userId);
// Handles case of repeated unarchival calls for the same package.
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 9a7916a..90d6adc 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -17,6 +17,7 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -25,6 +26,7 @@
import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
import static com.android.server.pm.PackageManagerService.TAG;
+import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
@@ -36,6 +38,7 @@
import com.android.internal.pm.parsing.pkg.ParsedPackage;
import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.SystemConfig;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.utils.WatchedLongSparseArray;
@@ -53,14 +56,17 @@
* as install) led to the request.
*/
final class ReconcilePackageUtils {
- private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
+ // TODO(b/308573259): with allow-list, we should be able to disallow such installs even in
+ // debuggable builds.
+ private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS = Build.IS_DEBUGGABLE
+ || !Flags.restrictNonpreloadsSystemShareduids();
public static List<ReconciledPackage> reconcilePackages(
List<InstallRequest> installRequests,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
- KeySetManagerService ksms, Settings settings)
+ KeySetManagerService ksms, Settings settings, SystemConfig systemConfig)
throws ReconcileFailure {
final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
@@ -187,11 +193,19 @@
SigningDetails.CertCapabilities.PERMISSION)) {
Slog.d(TAG, "Non-preload app associated with system signature: "
+ signatureCheckPs.getPackageName());
- if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
- throw new ReconcileFailure(
- INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
- "Non-preload app associated with system signature: "
- + signatureCheckPs.getPackageName());
+ if (sharedUserSetting != null && !ALLOW_NON_PRELOADS_SYSTEM_SHAREDUIDS) {
+ // Check the allow-list.
+ var allowList = systemConfig.getPackageToSharedUidAllowList();
+ var sharedUidName = allowList.get(signatureCheckPs.getPackageName());
+ if (sharedUidName == null
+ || !sharedUserSetting.name.equals(sharedUidName)) {
+ var msg = "Non-preload app " + signatureCheckPs.getPackageName()
+ + " signed with platform signature and joining shared uid: "
+ + sharedUserSetting.name;
+ Slog.e(TAG, msg + ", allowList: " + allowList);
+ throw new ReconcileFailure(
+ INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID, msg);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index aaf21c8..59d6219 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3386,12 +3386,7 @@
} else if (tagName.equals("verifier")) {
final String deviceIdentity = parser.getAttributeValue(null, "device");
- try {
- mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
- } catch (IllegalArgumentException e) {
- Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
- + e.getMessage());
- }
+ mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
// No longer used.
} else if (tagName.equals("keyset-settings")) {
@@ -3419,7 +3414,8 @@
}
str.close();
- } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException
+ | IllegalArgumentException e) {
// Remove corrupted file and retry.
atomicFile.failRead(str, e);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 211b754..4c653f6 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2833,7 +2833,8 @@
@VisibleForTesting
boolean areShortcutsSupportedOnHomeScreen(@UserIdInt int userId) {
- if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()) {
+ if (!android.os.Flags.allowPrivateProfile() || !Flags.disablePrivateSpaceItemsOnHome()
+ || !android.multiuser.Flags.enablePrivateSpaceFeatures()) {
return true;
}
final long start = getStatStartTime();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7349755..88e7596 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -21,10 +21,15 @@
import static android.content.Intent.EXTRA_USER_ID;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
import static com.android.internal.app.SetScreenLockDialogActivity.LAUNCH_REASON_DISABLE_QUIET_MODE;
@@ -1006,9 +1011,17 @@
emulateSystemUserModeIfNeeded();
}
+ private boolean doesDeviceHardwareSupportPrivateSpace() {
+ return !mPm.hasSystemFeature(FEATURE_EMBEDDED, 0)
+ && !mPm.hasSystemFeature(FEATURE_WATCH, 0)
+ && !mPm.hasSystemFeature(FEATURE_LEANBACK, 0)
+ && !mPm.hasSystemFeature(FEATURE_AUTOMOTIVE, 0);
+ }
+
private static boolean isAutoLockForPrivateSpaceEnabled() {
return android.os.Flags.allowPrivateProfile()
- && Flags.supportAutolockForPrivateSpace();
+ && Flags.supportAutolockForPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
void systemReady() {
@@ -1052,7 +1065,8 @@
private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() {
return android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts();
+ && android.multiuser.Flags.enablePrivateSpaceAutolockOnRestarts()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures();
}
/**
@@ -1493,7 +1507,8 @@
private boolean isProfileHidden(int userId) {
UserProperties userProperties = getUserPropertiesCopy(userId);
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableHidingProfiles()) {
+ && android.multiuser.Flags.enableHidingProfiles()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
return userProperties.getProfileApiVisibility()
== UserProperties.PROFILE_API_VISIBILITY_HIDDEN;
}
@@ -1693,7 +1708,8 @@
setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
return true;
}
- if (android.os.Flags.allowPrivateProfile()) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
final UserProperties userProperties = getUserPropertiesInternal(userId);
if (userProperties != null
&& userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
@@ -1839,7 +1855,8 @@
logQuietModeEnabled(userId, enableQuietMode, callingPackage);
// Broadcast generic intents for all profiles
- if (android.os.Flags.allowPrivateProfile()) {
+ if (android.os.Flags.allowPrivateProfile()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
broadcastProfileAvailabilityChanges(profile, parent.getUserHandle(),
enableQuietMode, false);
}
@@ -1852,7 +1869,8 @@
private void stopUserForQuietMode(int userId) throws RemoteException {
if (android.os.Flags.allowPrivateProfile()
- && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
+ && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
+ && android.multiuser.Flags.enablePrivateSpaceFeatures()) {
// Allow delayed locking since some profile types want to be able to unlock again via
// biometrics.
ActivityManager.getService()
@@ -2751,6 +2769,18 @@
}
@Override
+ public boolean canAddPrivateProfile(@UserIdInt int userId) {
+ checkCreateUsersPermission("canHaveRestrictedProfile");
+ UserInfo parentUserInfo = getUserInfo(userId);
+ return isUserTypeEnabled(USER_TYPE_PROFILE_PRIVATE)
+ && canAddMoreProfilesToUser(USER_TYPE_PROFILE_PRIVATE,
+ userId, /* allowedToRemoveOne */ false)
+ && (parentUserInfo != null && parentUserInfo.isMain())
+ && doesDeviceHardwareSupportPrivateSpace()
+ && !hasUserRestriction(UserManager.DISALLOW_ADD_PRIVATE_PROFILE, userId);
+ }
+
+ @Override
public boolean hasRestrictedProfiles(@UserIdInt int userId) {
checkManageUsersPermission("hasRestrictedProfiles");
synchronized (mUsersLock) {
@@ -5308,7 +5338,7 @@
if (!isUserTypeEnabled(userTypeDetails)) {
throwCheckedUserOperationException(
"Cannot add a user of disabled type " + userType + ".",
- UserManager.USER_OPERATION_ERROR_MAX_USERS);
+ UserManager.USER_OPERATION_ERROR_DISABLED_USER);
}
synchronized (mUsersLock) {
@@ -5341,6 +5371,7 @@
final boolean isDemo = UserManager.isUserTypeDemo(userType);
final boolean isManagedProfile = UserManager.isUserTypeManagedProfile(userType);
final boolean isCommunalProfile = UserManager.isUserTypeCommunalProfile(userType);
+ final boolean isPrivateProfile = UserManager.isUserTypePrivateProfile(userType);
final long ident = Binder.clearCallingIdentity();
UserInfo userInfo;
@@ -5387,6 +5418,12 @@
+ " for user " + parentId,
UserManager.USER_OPERATION_ERROR_MAX_USERS);
}
+ if (android.multiuser.Flags.blockPrivateSpaceCreation()
+ && isPrivateProfile && !canAddPrivateProfile(parentId)) {
+ throwCheckedUserOperationException(
+ "Cannot add profile of type " + userType + " for user " + parentId,
+ UserManager.USER_OPERATION_ERROR_PRIVATE_PROFILE);
+ }
if (isRestricted && (parentId != UserHandle.USER_SYSTEM)
&& !isCreationOverrideEnabled()) {
throwCheckedUserOperationException(
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 114daaa..7f9c1cf 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -292,6 +292,7 @@
.setName(USER_TYPE_PROFILE_PRIVATE)
.setBaseType(FLAG_PROFILE)
.setMaxAllowedPerParent(1)
+ .setEnabled(UserManager.isPrivateProfileEnabled() ? 1 : 0)
.setLabels(R.string.profile_label_private)
.setIconBadge(com.android.internal.R.drawable.ic_private_profile_icon_badge)
.setBadgePlain(com.android.internal.R.drawable.ic_private_profile_badge)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 47032ea..754b141 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -408,6 +408,9 @@
/**
* Gets the permission states for requested package, persistent device and user.
+ * <p>
+ * <strong>Note: </strong>Default device permissions are not inherited in this API. Returns the
+ * exact permission states for the requested device.
*
* @param packageName name of the package you are checking against
* @param deviceId id of the persistent device you are checking against
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
index 305b087..5c8215e 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java
@@ -16,6 +16,10 @@
package com.android.server.pm.verify.domain;
+import static android.content.IntentFilter.WILDCARD;
+
+import static com.android.server.pm.verify.domain.DomainVerificationUtils.isValidDomain;
+
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -253,9 +257,18 @@
Map<String, List<UriRelativeFilterGroup>> domainToGroupsMap =
pkgState.getUriRelativeFilterGroupMap();
for (String domain : bundle.keySet()) {
+ if (!isValidDomain(domain)) {
+ continue;
+ }
ArrayList<UriRelativeFilterGroupParcel> parcels =
bundle.getParcelableArrayList(domain, UriRelativeFilterGroupParcel.class);
- domainToGroupsMap.put(domain, UriRelativeFilterGroup.parcelsToGroups(parcels));
+ List<UriRelativeFilterGroup> groups =
+ UriRelativeFilterGroup.parcelsToGroups(parcels);
+ if (groups == null || groups.isEmpty()) {
+ domainToGroupsMap.remove(domain);
+ } else {
+ domainToGroupsMap.put(domain, groups);
+ }
}
}
}
@@ -273,9 +286,11 @@
Map<String, List<UriRelativeFilterGroup>> map =
pkgState.getUriRelativeFilterGroupMap();
for (int i = 0; i < domains.size(); i++) {
- List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
- bundle.putParcelableList(domains.get(i),
- UriRelativeFilterGroup.groupsToParcels(groups));
+ if (map.containsKey(domains.get(i))) {
+ List<UriRelativeFilterGroup> groups = map.get(domains.get(i));
+ bundle.putParcelableList(domains.get(i),
+ UriRelativeFilterGroup.groupsToParcels(groups));
+ }
}
}
}
@@ -285,15 +300,29 @@
@NonNull
private List<UriRelativeFilterGroup> getUriRelativeFilterGroups(@NonNull String packageName,
@NonNull String domain) {
- List<UriRelativeFilterGroup> groups = Collections.emptyList();
+ List<UriRelativeFilterGroup> groups;
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState != null) {
- groups = pkgState.getUriRelativeFilterGroupMap().getOrDefault(domain,
- Collections.emptyList());
+ Map<String, List<UriRelativeFilterGroup>> groupMap =
+ pkgState.getUriRelativeFilterGroupMap();
+ groups = groupMap.get(domain);
+ if (groups != null) {
+ return groups;
+ }
+ int first = domain.indexOf(".");
+ int second = domain.indexOf('.', first + 1);
+ while (first > 0 && second > 0) {
+ groups = groupMap.get(WILDCARD + domain.substring(first));
+ if (groups != null) {
+ return groups;
+ }
+ first = second;
+ second = domain.indexOf('.', second + 1);
+ }
}
}
- return groups;
+ return Collections.emptyList();
}
@NonNull
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
index 3fd00c6..b8c4d22 100644
--- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
+++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java
@@ -35,6 +35,9 @@
public final class DomainVerificationUtils {
+ public static final int MAX_DOMAIN_LENGTH = 254;
+ public static final int MAX_DOMAIN_LABEL_LENGTH = 63;
+
private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial(
() -> Patterns.DOMAIN_NAME.matcher(""));
@@ -108,4 +111,41 @@
appInfo.targetSdkVersion = pkg.getTargetSdkVersion();
return appInfo;
}
+
+ static boolean isValidDomain(String domain) {
+ if (domain.length() > MAX_DOMAIN_LENGTH || domain.equals("*")) {
+ return false;
+ }
+ if (domain.charAt(0) == '*') {
+ if (domain.charAt(1) != '.') {
+ return false;
+ }
+ domain = domain.substring(2);
+ }
+ int labels = 1;
+ int labelStart = -1;
+ for (int i = 0; i < domain.length(); i++) {
+ char c = domain.charAt(i);
+ if (c == '.') {
+ int labelLength = i - labelStart - 1;
+ if (labelLength == 0 || labelLength > MAX_DOMAIN_LABEL_LENGTH) {
+ return false;
+ }
+ labelStart = i;
+ labels += 1;
+ } else if (!isValidDomainChar(c)) {
+ return false;
+ }
+ }
+ int lastLabelLength = domain.length() - labelStart - 1;
+ if (lastLabelLength == 0 || lastLabelLength > 63) {
+ return false;
+ }
+ return labels > 1;
+ }
+
+ private static boolean isValidDomainChar(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9') || c == '-';
+ }
}
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index 32a21c5..cebf7fb 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -21,7 +21,6 @@
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
import static android.app.AppOpsManager.OP_NONE;
-import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
@@ -148,7 +147,7 @@
pkg.hasPreserveLegacyExternalStorage();
targetSDK = getMinimumTargetSDK(context, appInfo, user);
- shouldApplyRestriction = (flags & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
+ shouldApplyRestriction = !isWhiteListed;
isForcedScopedStorage = sForcedScopedStorageAppWhitelist
.contains(appInfo.packageName);
} else {
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 24d7acd..5360788 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -700,6 +700,8 @@
return runOverrideStatus();
case "reset":
return runReset();
+ case "headroom":
+ return runHeadroom();
default:
return handleDefaultCommands(cmd);
}
@@ -862,6 +864,36 @@
}
}
+ private int runHeadroom() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final PrintWriter pw = getOutPrintWriter();
+ int forecastSecs;
+ try {
+ forecastSecs = Integer.parseInt(getNextArgRequired());
+ } catch (RuntimeException ex) {
+ pw.println("Error: " + ex);
+ return -1;
+ }
+ if (!mHalReady.get()) {
+ pw.println("Error: thermal HAL is not ready");
+ return -1;
+ }
+
+ if (forecastSecs < MIN_FORECAST_SEC || forecastSecs > MAX_FORECAST_SEC) {
+ pw.println(
+ "Error: forecast second input should be in range [" + MIN_FORECAST_SEC
+ + "," + MAX_FORECAST_SEC + "]");
+ return -1;
+ }
+ float headroom = mTemperatureWatcher.getForecast(forecastSecs);
+ pw.println("Headroom in " + forecastSecs + " seconds: " + headroom);
+ return 0;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -877,6 +909,9 @@
pw.println(" status code is defined in android.os.Temperature.");
pw.println(" reset");
pw.println(" unlocks the thermal status of the device.");
+ pw.println(" headroom FORECAST_SECONDS");
+ pw.println(" gets the thermal headroom forecast in specified seconds, from ["
+ + MIN_FORECAST_SEC + "," + MAX_FORECAST_SEC + "].");
pw.println();
}
}
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index f8c678a..52ef87c 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -45,11 +45,9 @@
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static android.hardware.SensorPrivacyManager.StateTypes.AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static android.hardware.SensorPrivacyManager.StateTypes.DISABLED;
import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED;
+import static android.hardware.SensorPrivacyManager.StateTypes.ENABLED_EXCEPT_ALLOWLISTED_APPS;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_HARDWARE;
import static android.hardware.SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE;
import static android.os.UserHandle.USER_NULL;
@@ -57,11 +55,9 @@
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__ACTION_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
-import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
+import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__CAMERA;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__MICROPHONE;
import static com.android.internal.util.FrameworkStatsLog.PRIVACY_SENSOR_TOGGLE_INTERACTION__SENSOR__SENSOR_UNKNOWN;
@@ -98,7 +94,6 @@
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.drawable.Icon;
-import android.hardware.CameraPrivacyAllowlistEntry;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.hardware.SensorPrivacyManager;
@@ -153,7 +148,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
@@ -170,18 +164,12 @@
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
- private static final int ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS =
- PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
- @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_ON =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
+ private static final int ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS =
+ PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
+ @FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
private static final int ACTION__TOGGLE_OFF =
PRIVACY_SENSOR_TOGGLE_INTERACTION__ACTION__TOGGLE_OFF;
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@@ -208,8 +196,7 @@
private CallStateHelper mCallStateHelper;
private KeyguardManager mKeyguardManager;
- List<CameraPrivacyAllowlistEntry> mCameraPrivacyAllowlist =
- new ArrayList<CameraPrivacyAllowlistEntry>();
+ List<String> mCameraPrivacyAllowlist = new ArrayList<String>();
private int mCurrentUser = USER_NULL;
@@ -227,14 +214,8 @@
mPackageManagerInternal = getLocalService(PackageManagerInternal.class);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
- ArrayMap<String, Boolean> cameraPrivacyAllowlist =
- SystemConfig.getInstance().getCameraPrivacyAllowlist();
-
- for (Map.Entry<String, Boolean> entry : cameraPrivacyAllowlist.entrySet()) {
- CameraPrivacyAllowlistEntry ent = new CameraPrivacyAllowlistEntry();
- ent.packageName = entry.getKey();
- ent.isMandatory = entry.getValue();
- mCameraPrivacyAllowlist.add(ent);
+ for (String entry : SystemConfig.getInstance().getCameraPrivacyAllowlist()) {
+ mCameraPrivacyAllowlist.add(entry);
}
}
@@ -908,14 +889,8 @@
case DISABLED :
logAction = ACTION__TOGGLE_ON;
break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS;
- break;
- case AUTOMOTIVE_DRIVER_ASSISTANCE_APPS :
- logAction = ACTION__AUTOMOTIVE_DRIVER_ASSISTANCE_APPS;
+ case ENABLED_EXCEPT_ALLOWLISTED_APPS :
+ logAction = ACTION__TOGGLE_ON_EXCEPT_ALLOWLISTED_APPS;
break;
default :
logAction = ACTION__ACTION_UNKNOWN;
@@ -981,11 +956,23 @@
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
- public List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist() {
+ public List<String> getCameraPrivacyAllowlist() {
enforceObserveSensorPrivacyPermission();
return mCameraPrivacyAllowlist;
}
+ /**
+ * Sets camera privacy allowlist.
+ * @param allowlist List of automotive driver assistance packages for
+ * privacy allowlisting.
+ * @hide
+ */
+ @Override
+ public void setCameraPrivacyAllowlist(List<String> allowlist) {
+ enforceManageSensorPrivacyPermission();
+ mCameraPrivacyAllowlist = new ArrayList<>(allowlist);
+ }
+
@Override
@FlaggedApi(Flags.FLAG_CAMERA_PRIVACY_ALLOWLIST)
@RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY)
@@ -1005,23 +992,9 @@
return true;
} else if (state == DISABLED) {
return false;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && !entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if ((packageName.equals(entry.packageName)) && entry.isMandatory) {
- return false;
- }
- }
- return true;
- } else if (state == AUTOMOTIVE_DRIVER_ASSISTANCE_APPS) {
- for (CameraPrivacyAllowlistEntry entry : mCameraPrivacyAllowlist) {
- if (packageName.equals(entry.packageName)) {
+ } else if (state == ENABLED_EXCEPT_ALLOWLISTED_APPS) {
+ for (String entry : mCameraPrivacyAllowlist) {
+ if (packageName.equals(entry)) {
return false;
}
}
@@ -1616,7 +1589,7 @@
setToggleSensorPrivacy(userId, SHELL, sensor, false);
}
break;
- case "automotive_driver_assistance_apps" : {
+ case "enable_except_allowlisted_apps" : {
if (Flags.cameraPrivacyAllowlist()) {
int sensor = sensorStrToId(getNextArgRequired());
if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
@@ -1625,33 +1598,7 @@
}
setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_helpful_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS);
- }
- }
- break;
- case "automotive_driver_assistance_required_apps" : {
- if (Flags.cameraPrivacyAllowlist()) {
- int sensor = sensorStrToId(getNextArgRequired());
- if ((!isAutomotive(mContext)) || (sensor != CAMERA)) {
- pw.println("Command not valid for this sensor");
- return -1;
- }
-
- setToggleSensorPrivacyState(userId, SHELL, sensor,
- AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS);
+ ENABLED_EXCEPT_ALLOWLISTED_APPS);
}
}
break;
@@ -1679,18 +1626,9 @@
pw.println("");
if (Flags.cameraPrivacyAllowlist()) {
if (isAutomotive(mContext)) {
- pw.println(" automotive_driver_assistance_apps USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which help you"
- + " drive and apps which are required by OEM");
- pw.println("");
- pw.println(" automotive_driver_assistance_helpful_apps "
+ pw.println(" enable_except_allowlisted_apps "
+ "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which "
- + "help you drive.");
- pw.println("");
- pw.println(" automotive_driver_assistance_required_apps "
- + "USER_ID SENSOR");
- pw.println(" Disable privacy for automotive apps which are "
+ pw.println(" Enable privacy except for automotive apps which are "
+ "required by OEM.");
pw.println("");
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index ffce50e..79adcb4 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -349,7 +349,6 @@
}
}
- userState.mIAppMap.clear();
userState.mAdServiceMap = adServiceMap;
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index b490f57..6fc9d9a 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -208,6 +208,7 @@
* potentially expensive or resource-linked objects, such as {@link IBinder}.
*/
static final class DebugInfo {
+ final Status mStatus;
final long mCreateTime;
final CallerInfo mCallerInfo;
@Nullable
@@ -220,7 +221,6 @@
private final CombinedVibration mOriginalEffect;
private final int mScaleLevel;
private final float mAdaptiveScale;
- private final Status mStatus;
DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration playedEffect,
@Nullable CombinedVibration originalEffect, int scaleLevel,
@@ -253,6 +253,10 @@
+ ", callerInfo: " + mCallerInfo;
}
+ void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+ statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale);
+ }
+
/**
* Write this info in a compact way into given {@link PrintWriter}.
*
diff --git a/services/core/java/com/android/server/vibrator/VibrationSettings.java b/services/core/java/com/android/server/vibrator/VibrationSettings.java
index 5b77433..2fc183d 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSettings.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSettings.java
@@ -532,7 +532,7 @@
return false;
}
- if (Flags.keyboardCategoryEnabled()) {
+ if (Flags.keyboardCategoryEnabled() && mVibrationConfig.hasFixedKeyboardAmplitude()) {
int category = callerInfo.attrs.getCategory();
if (usage == USAGE_TOUCH && category == CATEGORY_KEYBOARD) {
// Keyboard touch has a different user setting.
diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
index f510b4e..f3e226e 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java
@@ -40,8 +40,10 @@
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Creates and manages a queue of steps for performing a VibrationEffect, as well as coordinating
@@ -70,6 +72,7 @@
private final DeviceAdapter mDeviceAdapter;
private final VibrationScaler mVibrationScaler;
+ private final VibratorFrameworkStatsLogger mStatsLogger;
// Not guarded by lock because it's mostly used to read immutable fields by this conductor.
// This is only modified here at the prepareToStart method which always runs at the vibration
@@ -103,14 +106,15 @@
private int mSuccessfulVibratorOnSteps;
VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
- DeviceAdapter deviceAdapter,
- VibrationScaler vibrationScaler,
+ DeviceAdapter deviceAdapter, VibrationScaler vibrationScaler,
+ VibratorFrameworkStatsLogger statsLogger,
CompletableFuture<Void> requestVibrationParamsFuture,
VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
this.mVibration = vib;
this.vibrationSettings = vibrationSettings;
this.mDeviceAdapter = deviceAdapter;
mVibrationScaler = vibrationScaler;
+ mStatsLogger = statsLogger;
mRequestVibrationParamsFuture = requestVibrationParamsFuture;
this.vibratorManagerHooks = vibratorManagerHooks;
this.mSignalVibratorsComplete =
@@ -461,9 +465,19 @@
}
try {
- mRequestVibrationParamsFuture.orTimeout(
+ mRequestVibrationParamsFuture.get(
vibrationSettings.getRequestVibrationParamsTimeoutMs(),
- TimeUnit.MILLISECONDS).get();
+ TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Request for vibration params timed out", e);
+ }
+ mStatsLogger.logVibrationParamRequestTimeout(mVibration.callerInfo.uid);
+ } catch (CancellationException e) {
+ if (DEBUG) {
+ Slog.d(TAG, "Request for vibration params cancelled, maybe superseded or"
+ + " vibrator controller unregistered. Skipping params...", e);
+ }
} catch (Throwable e) {
Slog.w(TAG, "Failed to retrieve vibration params.", e);
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index ec3d99b..10317c9 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -72,19 +72,21 @@
private final VibrationParamsRecords mVibrationParamsRecords;
private final VibratorControllerHolder mVibratorControllerHolder;
private final VibrationScaler mVibrationScaler;
+ private final VibratorFrameworkStatsLogger mStatsLogger;
private final Object mLock;
private final int[] mRequestVibrationParamsForUsages;
@GuardedBy("mLock")
- private CompletableFuture<Void> mRequestVibrationParamsFuture = null;
- @GuardedBy("mLock")
- private IBinder mRequestVibrationParamsToken;
+ @Nullable
+ private VibrationParamRequest mVibrationParamRequest = null;
VibratorControlService(Context context,
VibratorControllerHolder vibratorControllerHolder, VibrationScaler vibrationScaler,
- VibrationSettings vibrationSettings, Object lock) {
+ VibrationSettings vibrationSettings, VibratorFrameworkStatsLogger statsLogger,
+ Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
mVibrationScaler = vibrationScaler;
+ mStatsLogger = statsLogger;
mLock = lock;
mRequestVibrationParamsForUsages = vibrationSettings.getRequestVibrationParamsForUsages();
@@ -180,20 +182,25 @@
Objects.requireNonNull(requestToken);
synchronized (mLock) {
- if (mRequestVibrationParamsToken == null) {
+ if (mVibrationParamRequest == null) {
Slog.wtf(TAG,
"New vibration params received but no token was cached in the service. "
+ "New vibration params ignored.");
+ mStatsLogger.logVibrationParamResponseIgnored();
return;
}
- if (!Objects.equals(requestToken, mRequestVibrationParamsToken)) {
+ if (!Objects.equals(requestToken, mVibrationParamRequest.token)) {
Slog.w(TAG,
"New vibration params received but the provided token does not match the "
+ "cached one. New vibration params ignored.");
+ mStatsLogger.logVibrationParamResponseIgnored();
return;
}
+ long latencyMs = SystemClock.uptimeMillis() - mVibrationParamRequest.uptimeMs;
+ mStatsLogger.logVibrationParamRequestLatency(mVibrationParamRequest.uid, latencyMs);
+
updateAdaptiveHapticsScales(result);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ false);
recordUpdateVibrationParams(result, /* fromRequest= */ true);
@@ -222,7 +229,7 @@
*/
@Nullable
public CompletableFuture<Void> triggerVibrationParamsRequest(
- @VibrationAttributes.Usage int usage, int timeoutInMillis) {
+ int uid, @VibrationAttributes.Usage int usage, int timeoutInMillis) {
synchronized (mLock) {
IVibratorController vibratorController =
mVibratorControllerHolder.getVibratorController();
@@ -241,16 +248,16 @@
try {
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
- mRequestVibrationParamsFuture = new CompletableFuture<>();
- mRequestVibrationParamsToken = new Binder();
+ mVibrationParamRequest = new VibrationParamRequest(uid);
vibratorController.requestVibrationParams(vibrationType, timeoutInMillis,
- mRequestVibrationParamsToken);
+ mVibrationParamRequest.token);
+ return mVibrationParamRequest.future;
} catch (RemoteException e) {
Slog.e(TAG, "Failed to request vibration params.", e);
endOngoingRequestVibrationParamsLocked(/* wasCancelled= */ true);
}
- return mRequestVibrationParamsFuture;
+ return null;
}
}
@@ -276,13 +283,13 @@
}
/**
- * Returns the {@link #mRequestVibrationParamsToken} which is used to validate
+ * Returns the binder token which is used to validate
* {@link #onRequestVibrationParamsComplete(IBinder, VibrationParam[])} calls.
*/
@VisibleForTesting
public IBinder getRequestVibrationParamsToken() {
synchronized (mLock) {
- return mRequestVibrationParamsToken;
+ return mVibrationParamRequest == null ? null : mVibrationParamRequest.token;
}
}
@@ -293,7 +300,7 @@
synchronized (mLock) {
isVibratorControllerRegistered =
mVibratorControllerHolder.getVibratorController() != null;
- hasPendingVibrationParamsRequest = mRequestVibrationParamsFuture != null;
+ hasPendingVibrationParamsRequest = mVibrationParamRequest != null;
}
pw.println("VibratorControlService:");
@@ -329,18 +336,10 @@
*/
@GuardedBy("mLock")
private void endOngoingRequestVibrationParamsLocked(boolean wasCancelled) {
- mRequestVibrationParamsToken = null;
- if (mRequestVibrationParamsFuture == null) {
- return;
+ if (mVibrationParamRequest != null) {
+ mVibrationParamRequest.endRequest(wasCancelled);
}
-
- if (wasCancelled) {
- mRequestVibrationParamsFuture.cancel(/* mayInterruptIfRunning= */ true);
- } else {
- mRequestVibrationParamsFuture.complete(null);
- }
-
- mRequestVibrationParamsFuture = null;
+ mVibrationParamRequest = null;
}
private static int mapToAdaptiveVibrationType(@VibrationAttributes.Usage int usage) {
@@ -423,6 +422,7 @@
* @param scale The scaling factor that should be applied to the vibrations.
*/
private void updateAdaptiveHapticsScales(int types, float scale) {
+ mStatsLogger.logVibrationParamScale(scale);
for (int usage : mapFromAdaptiveVibrationTypeToVibrationUsages(types)) {
updateOrRemoveAdaptiveHapticsScale(usage, scale);
}
@@ -504,6 +504,27 @@
}
}
+ /** Represents a request for {@link VibrationParam}. */
+ private static final class VibrationParamRequest {
+ public final CompletableFuture<Void> future = new CompletableFuture<>();
+ public final IBinder token = new Binder();
+ public final int uid;
+ public final long uptimeMs;
+
+ VibrationParamRequest(int uid) {
+ this.uid = uid;
+ uptimeMs = SystemClock.uptimeMillis();
+ }
+
+ public void endRequest(boolean wasCancelled) {
+ if (wasCancelled) {
+ future.cancel(/* mayInterruptIfRunning= */ true);
+ } else {
+ future.complete(null);
+ }
+ }
+ }
+
/**
* Record for a single {@link Vibration.DebugInfo}, that can be grouped by usage and aggregated
* by UID, {@link VibrationAttributes} and {@link VibrationEffect}.
diff --git a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
index 7e601b6..e9c3894 100644
--- a/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/vibrator/VibratorFrameworkStatsLogger.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.modules.expresslog.Counter;
+import com.android.modules.expresslog.Histogram;
import java.util.ArrayDeque;
import java.util.Queue;
@@ -40,6 +41,23 @@
// Warning about dropping entries after this amount of atoms were dropped by the throttle.
private static final int VIBRATION_REPORTED_WARNING_QUEUE_SIZE = 200;
+ // Latency between 0ms and 99ms, with 100 representing overflow latencies >= 100ms.
+ // Underflow not expected.
+ private static final Histogram sVibrationParamRequestLatencyHistogram = new Histogram(
+ "vibrator.value_vibration_param_request_latency",
+ new Histogram.UniformOptions(20, 0, 100));
+
+ // Scales in [0, 2), with 2 representing overflow scales >= 2.
+ // Underflow expected to represent how many times scales were cleared (set to -1).
+ private static final Histogram sVibrationParamScaleHistogram = new Histogram(
+ "vibrator.value_vibration_param_scale", new Histogram.UniformOptions(20, 0, 2));
+
+ // Scales in [0, 2), with 2 representing overflow scales >= 2.
+ // Underflow not expected.
+ private static final Histogram sAdaptiveHapticScaleHistogram = new Histogram(
+ "vibrator.value_vibration_adaptive_haptic_scale",
+ new Histogram.UniformOptions(20, 0, 2));
+
private final Object mLock = new Object();
private final Handler mHandler;
private final long mVibrationReportedLogIntervalMillis;
@@ -140,6 +158,33 @@
}
}
+ /** Logs adaptive haptic scale value applied to a vibration, only if it's not 1.0. */
+ public void logVibrationAdaptiveHapticScale(int uid, float scale) {
+ if (Float.compare(scale, 1f) != 0) {
+ sAdaptiveHapticScaleHistogram.logSampleWithUid(uid, scale);
+ }
+ }
+
+ /** Logs a vibration param scale value received by the vibrator control service. */
+ public void logVibrationParamScale(float scale) {
+ sVibrationParamScaleHistogram.logSample(scale);
+ }
+
+ /** Logs the latency of a successful vibration params request completed before a vibration. */
+ public void logVibrationParamRequestLatency(int uid, long latencyMs) {
+ sVibrationParamRequestLatencyHistogram.logSampleWithUid(uid, (float) latencyMs);
+ }
+
+ /** Logs a vibration params request timed out before a vibration. */
+ public void logVibrationParamRequestTimeout(int uid) {
+ Counter.logIncrementWithUid("vibrator.value_vibration_param_request_timeout", uid);
+ }
+
+ /** Logs when a response received for a vibration params request is ignored by the service. */
+ public void logVibrationParamResponseIgnored() {
+ Counter.logIncrement("vibrator.value_vibration_param_response_ignored");
+ }
+
/** Logs only if the haptics feedback effect is one of the KEYBOARD_ constants. */
public static void logPerformHapticsFeedbackIfKeyboard(int uid, int hapticsFeedbackEffect) {
boolean isKeyboard;
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index c1bf039..af494a3 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -212,12 +212,13 @@
mContext = context;
mInjector = injector;
mHandler = injector.createHandler(Looper.myLooper());
+ mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mVibrationSettings = new VibrationSettings(mContext, mHandler);
mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
mVibratorControlService = new VibratorControlService(mContext,
injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings,
- mLock);
+ mFrameworkStatsLogger, mLock);
mInputDeviceDelegate = new InputDeviceDelegate(mContext, mHandler);
VibrationCompleteListener listener = new VibrationCompleteListener(this);
@@ -235,7 +236,6 @@
recentDumpSizeLimit, dumpSizeLimit, dumpAggregationTimeLimit);
mBatteryStatsService = injector.getBatteryStatsService();
- mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler);
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -853,9 +853,7 @@
private void endVibrationLocked(HalVibration vib, Vibration.EndInfo vibrationEndInfo,
boolean shouldWriteStats) {
vib.end(vibrationEndInfo);
- logVibrationStatus(vib.callerInfo.uid, vib.callerInfo.attrs,
- vibrationEndInfo.status);
- mVibratorManagerRecords.record(vib);
+ logAndRecordVibration(vib.getDebugInfo());
if (shouldWriteStats) {
mFrameworkStatsLogger.writeVibrationReportedAsync(
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
@@ -866,9 +864,7 @@
private void endVibrationAndWriteStatsLocked(ExternalVibrationHolder vib,
Vibration.EndInfo vibrationEndInfo) {
vib.end(vibrationEndInfo);
- logVibrationStatus(vib.externalVibration.getUid(),
- vib.externalVibration.getVibrationAttributes(), vibrationEndInfo.status);
- mVibratorManagerRecords.record(vib);
+ logAndRecordVibration(vib.getDebugInfo());
mFrameworkStatsLogger.writeVibrationReportedAsync(
vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis()));
}
@@ -882,12 +878,12 @@
vib.callerInfo.attrs.getUsage())) {
requestVibrationParamsFuture =
mVibratorControlService.triggerVibrationParamsRequest(
- vib.callerInfo.attrs.getUsage(),
+ vib.callerInfo.uid, vib.callerInfo.attrs.getUsage(),
mVibrationSettings.getRequestVibrationParamsTimeoutMs());
}
return new VibrationStepConductor(vib, mVibrationSettings, mDeviceAdapter, mVibrationScaler,
- requestVibrationParamsFuture, mVibrationThreadCallbacks);
+ mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks);
}
private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) {
@@ -903,6 +899,12 @@
return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
}
+ private void logAndRecordVibration(Vibration.DebugInfo info) {
+ info.logMetrics(mFrameworkStatsLogger);
+ logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
+ mVibratorManagerRecords.record(info);
+ }
+
private void logVibrationStatus(int uid, VibrationAttributes attrs,
Vibration.Status status) {
switch (status) {
@@ -1752,15 +1754,7 @@
new VibrationRecords(recentVibrationSizeLimit, /* aggregationTimeLimit= */ 0);
}
- synchronized void record(HalVibration vib) {
- record(vib.getDebugInfo());
- }
-
- synchronized void record(ExternalVibrationHolder vib) {
- record(vib.getDebugInfo());
- }
-
- private synchronized void record(Vibration.DebugInfo info) {
+ synchronized void record(Vibration.DebugInfo info) {
GroupedAggregatedLogRecords.AggregatedLogRecord<VibrationRecord> droppedRecord =
mRecentVibrations.add(new VibrationRecord(info));
if (droppedRecord != null) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
index 601c7f4..5175b74 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java
@@ -39,6 +39,7 @@
import android.view.DisplayInfo;
import android.view.View;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.utils.TimingsTraceAndSlog;
import libcore.io.IoUtils;
@@ -65,7 +66,7 @@
* Maximum acceptable parallax.
* A value of 1 means "the additional width for parallax is at most 100% of the screen width"
*/
- private static final float MAX_PARALLAX = 1f;
+ @VisibleForTesting static final float MAX_PARALLAX = 1f;
/**
* We define three ways to adjust a crop. These modes are used depending on the situation:
@@ -73,10 +74,9 @@
* - When going from folded to unfolded, we want to add content
* - For a screen rotation, we want to keep the same amount of content
*/
- private static final int ADD = 1;
- private static final int REMOVE = 2;
- private static final int BALANCE = 3;
-
+ @VisibleForTesting static final int ADD = 1;
+ @VisibleForTesting static final int REMOVE = 2;
+ @VisibleForTesting static final int BALANCE = 3;
private final WallpaperDisplayHelper mWallpaperDisplayHelper;
@@ -131,19 +131,23 @@
(bitmapSize.y - crop.height()) / 2);
return crop;
}
+
+ // If any suggested crop is invalid, fallback to case 1
+ for (int i = 0; i < suggestedCrops.size(); i++) {
+ Rect testCrop = suggestedCrops.valueAt(i);
+ if (testCrop == null || testCrop.left < 0 || testCrop.top < 0
+ || testCrop.right > bitmapSize.x || testCrop.bottom > bitmapSize.y) {
+ Slog.w(TAG, "invalid crop: " + testCrop + " for bitmap size: " + bitmapSize);
+ return getCrop(displaySize, bitmapSize, new SparseArray<>(), rtl);
+ }
+ }
+
int orientation = getOrientation(displaySize);
// Case 2: if the orientation exists in the suggested crops, adjust the suggested crop
Rect suggestedCrop = suggestedCrops.get(orientation);
if (suggestedCrop != null) {
- if (suggestedCrop.left < 0 || suggestedCrop.top < 0
- || suggestedCrop.right > bitmapSize.x || suggestedCrop.bottom > bitmapSize.y) {
- Slog.w(TAG, "invalid suggested crop: " + suggestedCrop);
- Rect fullImage = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
- return getAdjustedCrop(fullImage, bitmapSize, displaySize, true, rtl, ADD);
- } else {
return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD);
- }
}
// Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and
@@ -209,7 +213,8 @@
* Given a crop, a displaySize for the orientation of that crop, compute the visible part of the
* crop. This removes any additional width used for parallax. No-op if displaySize == null.
*/
- private static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
+ @VisibleForTesting
+ static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) {
if (displaySize == null) return crop;
Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD);
// only keep the visible part (without parallax)
@@ -240,12 +245,14 @@
* </li>
* </ul>
*/
- private static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
+ @VisibleForTesting
+ static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize,
boolean parallax, boolean rtl, int mode) {
Rect adjustedCrop = new Rect(crop);
float cropRatio = ((float) crop.width()) / crop.height();
float screenRatio = ((float) screenSize.x) / screenSize.y;
- if (cropRatio >= screenRatio) {
+ if (cropRatio == screenRatio) return crop;
+ if (cropRatio > screenRatio) {
if (!parallax) {
// rotate everything 90 degrees clockwise, compute the result, and rotate back
int newLeft = bitmapSize.y - crop.bottom;
@@ -274,6 +281,7 @@
}
}
} else {
+ // TODO (b/281648899) the third case is not always correct, fix that.
int widthToAdd = mode == REMOVE ? 0
: mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width())
: (int) (0.5 + crop.height() - crop.width());
@@ -644,6 +652,9 @@
if (!success) {
Slog.e(TAG, "Unable to apply new wallpaper");
wallpaper.getCropFile().delete();
+ wallpaper.mCropHints.clear();
+ wallpaper.cropHint.set(0, 0, 0, 0);
+ wallpaper.mSampleSize = 1f;
}
if (wallpaper.getCropFile().exists()) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index 88e9672..0165d65 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -341,6 +341,7 @@
} else {
wallpaper.cropHint.set(totalCropHint);
}
+ wallpaper.mSampleSize = parser.getAttributeFloat(null, "sampleSize", 1f);
} else {
wallpaper.cropHint.set(totalCropHint);
}
@@ -493,6 +494,7 @@
out.attributeInt(null, "totalCropTop", wallpaper.cropHint.top);
out.attributeInt(null, "totalCropRight", wallpaper.cropHint.right);
out.attributeInt(null, "totalCropBottom", wallpaper.cropHint.bottom);
+ out.attributeFloat(null, "sampleSize", wallpaper.mSampleSize);
} else if (!multiCrop()) {
final DisplayData wpdData =
mWallpaperDisplayHelper.getDisplayDataOrCreate(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index e9c4096..043470f 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -30,6 +30,7 @@
import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import android.webkit.IWebViewUpdateService;
@@ -37,6 +38,7 @@
import android.webkit.WebViewProviderResponse;
import com.android.internal.util.DumpUtils;
+import com.android.modules.expresslog.Histogram;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -52,6 +54,14 @@
private static final String TAG = "WebViewUpdateService";
+ private static final Histogram sPrepareWebViewInSystemServerLatency = new Histogram(
+ "webview.value_prepare_webview_in_system_server_latency",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.5f));
+
+ private static final Histogram sAppWaitingForRelroCompletionDelay = new Histogram(
+ "webview.value_app_waiting_for_relro_completion_delay",
+ new Histogram.ScaledRangeOptions(20, 0, 1, 1.4f));
+
private BroadcastReceiver mWebViewUpdatedReceiver;
private WebViewUpdateServiceInterface mImpl;
@@ -132,7 +142,10 @@
}
public void prepareWebViewInSystemServer() {
+ long currentTimeMs = SystemClock.uptimeMillis();
mImpl.prepareWebViewInSystemServer();
+ sPrepareWebViewInSystemServerLatency.logSample(
+ (float) (SystemClock.uptimeMillis() - currentTimeMs));
}
private static String packageNameFromIntent(Intent intent) {
@@ -204,8 +217,12 @@
throw new IllegalStateException("Cannot create a WebView from the SystemServer");
}
+ long startTimeMs = SystemClock.uptimeMillis();
final WebViewProviderResponse webViewProviderResponse =
WebViewUpdateService.this.mImpl.waitForAndGetProvider();
+ long endTimeMs = SystemClock.uptimeMillis();
+ sAppWaitingForRelroCompletionDelay.logSample((float) (endTimeMs - startTimeMs));
+
if (webViewProviderResponse.packageInfo != null) {
grantVisibilityToCaller(
webViewProviderResponse.packageInfo.packageName, Binder.getCallingUid());
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 1d6ad6d..532ff98 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -23,12 +23,15 @@
import android.os.AsyncTask;
import android.os.Trace;
import android.os.UserHandle;
+import android.util.AndroidRuntimeException;
import android.util.Slog;
import android.webkit.UserPackage;
import android.webkit.WebViewFactory;
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -357,6 +360,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -388,9 +397,15 @@
@Override
public WebViewProviderInfo getDefaultWebViewPackage() {
- throw new IllegalStateException(
- "getDefaultWebViewPackage shouldn't be called if update_service_v2 flag is"
- + " disabled.");
+ for (WebViewProviderInfo provider : getWebViewPackages()) {
+ if (provider.availableByDefault) {
+ return provider;
+ }
+ }
+
+ // This should be unreachable because the config parser enforces that there is at least
+ // one availableByDefault provider.
+ throw new AndroidRuntimeException("No available by default WebView Provider.");
}
private static class ProviderAndPackageInfo {
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
index 1f9d265..fb338ba 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl2.java
@@ -31,6 +31,8 @@
import android.webkit.WebViewProviderInfo;
import android.webkit.WebViewProviderResponse;
+import com.android.modules.expresslog.Counter;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -412,6 +414,12 @@
mNumRelroCreationsFinished = 0;
mNumRelroCreationsStarted =
mSystemInterface.onWebViewProviderChanged(newPackage);
+ Counter.logIncrement("webview.value_on_webview_provider_changed_counter");
+ if (newPackage.packageName.equals(getDefaultWebViewPackage().packageName)) {
+ Counter.logIncrement(
+ "webview.value_on_webview_provider_changed_"
+ + "with_default_package_counter");
+ }
// If the relro creations finish before we know the number of started creations
// we will have to do any cleanup/notifying here.
checkIfRelrosDoneLocked();
@@ -479,6 +487,7 @@
* for all users, otherwise use the default provider.
*/
private PackageInfo findPreferredWebViewPackage() throws WebViewPackageMissingException {
+ Counter.logIncrement("webview.value_find_preferred_webview_package_counter");
// If the user has chosen provider, use that (if it's installed and enabled for all
// users).
String userChosenPackageName = mSystemInterface.getUserChosenWebViewProvider(mContext);
@@ -508,12 +517,15 @@
PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(mDefaultProvider);
if (validityResult(mDefaultProvider, packageInfo) == VALIDITY_OK) {
return packageInfo;
+ } else {
+ Counter.logIncrement("webview.value_default_webview_package_invalid_counter");
}
} catch (NameNotFoundException e) {
Slog.w(TAG, "Default WebView package (" + mDefaultProvider.packageName + ") not found");
}
// This should never happen during normal operation (only with modified system images).
+ Counter.logIncrement("webview.value_webview_not_usable_for_all_users_counter");
mAnyWebViewInstalled = false;
throw new WebViewPackageMissingException("Could not find a loadable WebView package");
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 91eff18..4189988 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1209,6 +1209,7 @@
private boolean mShown;
private boolean mLastSurfaceShown;
private int mAlpha;
+ private int mPreviousAlpha;
private volatile boolean mInvalidated;
@@ -1344,6 +1345,7 @@
// using WindowManagerGlobalLock. Grab copies of these values before
// drawing on the canvas so that drawing can be performed outside of the lock.
int alpha;
+ boolean redrawBounds;
Rect drawingRect = null;
Region drawingBounds = null;
synchronized (mService.mGlobalLock) {
@@ -1361,7 +1363,13 @@
mInvalidated = false;
alpha = mAlpha;
- if (alpha > 0) {
+ // For b/325863281, we should ensure the drawn border path is cleared when
+ // alpha = 0. Therefore, we cache the last used alpha when drawing as
+ // mPreviousAlpha and check it here. If mPreviousAlpha > 0, which means
+ // the border is showing now, then we should still redraw the clear path
+ // on the canvas so the border is cleared.
+ redrawBounds = mAlpha > 0 || mPreviousAlpha > 0;
+ if (redrawBounds) {
drawingBounds = new Region(mBounds);
// Empty dirty rectangle means unspecified.
if (mDirtyRect.isEmpty()) {
@@ -1378,7 +1386,7 @@
final boolean showSurface;
// Draw without holding WindowManagerGlobalLock.
- if (alpha > 0) {
+ if (redrawBounds) {
Canvas canvas = null;
try {
canvas = mSurface.lockCanvas(drawingRect);
@@ -1392,11 +1400,11 @@
mPaint.setAlpha(alpha);
canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
mSurface.unlockCanvasAndPost(canvas);
- showSurface = true;
- } else {
- showSurface = false;
+ mPreviousAlpha = alpha;
}
+ showSurface = alpha > 0;
+
if (showSurface && !mLastSurfaceShown) {
mTransaction.show(mSurfaceControl).apply();
mLastSurfaceShown = true;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 7d5aa96d..7ba953d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -553,7 +553,6 @@
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean delayedResume; // not yet resumed because of stopped app switches?
boolean finishing; // activity in pending finish list?
- int configChangeFlags; // which config values have changed
private boolean keysPaused; // has key dispatching been paused for it?
int launchMode; // the launch mode activity attribute.
int lockTaskLaunchMode; // the lockTaskMode manifest attribute, subject to override
@@ -659,7 +658,7 @@
boolean mVoiceInteraction;
- private int mPendingRelaunchCount;
+ int mPendingRelaunchCount;
long mRelaunchStartTime;
// True if we are current in the process of removing this app token from the display
@@ -1295,10 +1294,6 @@
if (mDeferHidingClient) {
pw.println(prefix + "mDeferHidingClient=" + mDeferHidingClient);
}
- if (configChangeFlags != 0) {
- pw.print(prefix); pw.print(" configChangeFlags=");
- pw.println(Integer.toHexString(configChangeFlags));
- }
if (mServiceConnectionsHolder != null) {
pw.print(prefix); pw.print("connections="); pw.println(mServiceConnectionsHolder);
}
@@ -3993,7 +3988,7 @@
// If the display does not have running activity, the configuration may need to be
// updated for restoring original orientation of the display.
if (next == null) {
- mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, mDisplayContent,
true /* deferResume */);
}
if (activityRemoved) {
@@ -4064,7 +4059,7 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- DestroyActivityItem.obtain(token, finishing, configChangeFlags));
+ DestroyActivityItem.obtain(token, finishing));
} catch (Exception e) {
// We can just ignore exceptions here... if the process has crashed, our death
// notification will clean things up.
@@ -4106,8 +4101,6 @@
}
}
- configChangeFlags = 0;
-
return removedFromHistory;
}
@@ -5026,7 +5019,7 @@
return PauseActivityItem.obtain(token);
case STOPPING:
case STOPPED:
- return StopActivityItem.obtain(token, configChangeFlags);
+ return StopActivityItem.obtain(token);
default:
// Do not send a result immediately if the activity is in state INITIALIZING,
// RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -6300,7 +6293,7 @@
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
- configChangeFlags, false /* dontReport */, mAutoEnteringPip));
+ false /* dontReport */, mAutoEnteringPip));
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
@@ -6470,12 +6463,6 @@
* state to match that fact.
*/
void completeResumeLocked() {
- final boolean wasVisible = mVisibleRequested;
- setVisibility(true);
- if (!wasVisible) {
- // Visibility has changed, so take a note of it so we call the TaskStackChangedListener
- mTaskSupervisor.mAppVisibilitiesChangedSinceLastPause = true;
- }
idle = false;
results = null;
if (newIntents != null && newIntents.size() > 0) {
@@ -6614,7 +6601,7 @@
EventLogTags.writeWmStopActivity(
mUserId, System.identityHashCode(this), shortComponentName);
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StopActivityItem.obtain(token, configChangeFlags));
+ StopActivityItem.obtain(token));
mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
} catch (Exception e) {
@@ -9858,7 +9845,6 @@
if (shouldRelaunchLocked(changes, mTmpConfig)) {
// Aha, the activity isn't handling the change, so DIE DIE DIE.
- configChangeFlags |= changes;
if (mVisible && mAtmService.mTmpUpdateConfigurationResult.mIsUpdating
&& !mTransitionController.isShellTransitionsEnabled()) {
startFreezingScreenLocked(app, mAtmService.mTmpUpdateConfigurationResult.changes);
@@ -9887,7 +9873,7 @@
ProtoLog.v(WM_DEBUG_STATES, "Config is relaunching invisible "
+ "activity %s called by %s", this, Debug.getCallers(4));
}
- relaunchActivityLocked(preserveWindow);
+ relaunchActivityLocked(preserveWindow, changes);
// All done... tell the caller we weren't able to keep this activity around.
return false;
@@ -10029,9 +10015,8 @@
| CONFIG_SCREEN_LAYOUT)) != 0;
}
- void relaunchActivityLocked(boolean preserveWindow) {
+ void relaunchActivityLocked(boolean preserveWindow, int configChangeFlags) {
if (mAtmService.mSuppressResizeConfigChanges && preserveWindow) {
- configChangeFlags = 0;
return;
}
if (!preserveWindow) {
@@ -10104,8 +10089,6 @@
// The activity may be waiting for stop, but that is no longer appropriate for it.
mTaskSupervisor.mStoppingActivities.remove(this);
-
- configChangeFlags = 0;
}
/**
@@ -10174,7 +10157,7 @@
// {@link ActivityTaskManagerService.activityStopped}).
try {
mAtmService.getLifecycleManager().scheduleTransactionItem(app.getThread(),
- StopActivityItem.obtain(token, 0 /* configChanges */));
+ StopActivityItem.obtain(token));
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown during restart " + this, e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 6ad056f..2c39c58 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1625,7 +1625,7 @@
final ActivityRecord currentTop = startedActivityRootTask.topRunningActivity();
if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
mRootWindowContainer.ensureVisibilityAndConfig(
- currentTop, currentTop.getDisplayId(), false /* deferResume */);
+ currentTop, currentTop.mDisplayContent, false /* deferResume */);
}
if (!avoidMoveToFront() && mDoResume && mRootWindowContainer
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 2cda1f5..826e332 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -842,7 +842,7 @@
// Deferring resume here because we're going to launch new activity shortly.
// We don't want to perform a redundant launch of the same record while ensuring
// configurations and trying to resume top activity of focused root task.
- mRootWindowContainer.ensureVisibilityAndConfig(r, r.getDisplayId(),
+ mRootWindowContainer.ensureVisibilityAndConfig(r, r.mDisplayContent,
true /* deferResume */);
}
@@ -1011,7 +1011,8 @@
if (andResume && readyToResume()) {
// As part of the process of launching, ActivityThread also performs
// a resume.
- rootTask.minimalResumeActivityLocked(r);
+ r.setState(RESUMED, "realStartActivityLocked");
+ r.completeResumeLocked();
} else {
// This activity is not starting in the resumed state... which should look like we asked
// it to pause+stop (but remain visible), and it has done so and reported back the
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cd96806..83f44d2 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -311,7 +311,7 @@
return mInsetsHint;
}
final WindowState win = mWindowContainer.asWindowState();
- if (win != null && win.mGivenInsetsPending && win.mAttrs.providedInsets == null) {
+ if (win != null && win.mGivenInsetsPending) {
return mInsetsHint;
}
if (mInsetsHintStale) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07a03eb..a75d3b6 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1742,30 +1742,21 @@
*
* @param starting The currently starting activity or {@code null} if there is
* none.
- * @param displayId The id of the display where operation is executed.
+ * @param displayContent The display where the operation is executed.
* @param deferResume Whether to defer resume while updating config.
- * @return 'true' if starting activity was kept or wasn't provided, 'false' if it was relaunched
- * because of configuration update.
*/
- boolean ensureVisibilityAndConfig(ActivityRecord starting, int displayId, boolean deferResume) {
+ void ensureVisibilityAndConfig(@Nullable ActivityRecord starting,
+ @NonNull DisplayContent displayContent, boolean deferResume) {
// First ensure visibility without updating the config just yet. We need this to know what
// activities are affecting configuration now.
// Passing null here for 'starting' param value, so that visibility of actual starting
// activity will be properly updated.
ensureActivitiesVisible(null /* starting */, false /* notifyClients */);
- if (displayId == INVALID_DISPLAY) {
- // The caller didn't provide a valid display id, skip updating config.
- return true;
- }
-
// Force-update the orientation from the WindowManager, since we need the true configuration
// to send to the client now.
- final DisplayContent displayContent = getDisplayContent(displayId);
- Configuration config = null;
- if (displayContent != null) {
- config = displayContent.updateOrientation(starting, true /* forceUpdate */);
- }
+ final Configuration config =
+ displayContent.updateOrientation(starting, true /* forceUpdate */);
// Visibilities may change so let the starting activity have a chance to report. Can't do it
// when visibility is changed in each AppWindowToken because it may trigger wrong
// configuration push because the visibility of some activities may not be updated yet.
@@ -1773,13 +1764,8 @@
starting.reportDescendantOrientationChangeIfNeeded();
}
- if (displayContent != null) {
- // Update the configuration of the activities on the display.
- return displayContent.updateDisplayOverrideConfigurationLocked(config, starting,
- deferResume);
- } else {
- return true;
- }
+ // Update the configuration of the activities on the display.
+ displayContent.updateDisplayOverrideConfigurationLocked(config, starting, deferResume);
}
/**
diff --git a/services/core/java/com/android/server/wm/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java
index 64d8c75..8680436 100644
--- a/services/core/java/com/android/server/wm/SnapshotCache.java
+++ b/services/core/java/com/android/server/wm/SnapshotCache.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
import android.util.ArrayMap;
import android.window.TaskSnapshot;
@@ -93,10 +92,6 @@
if (entry != null) {
mAppIdMap.remove(entry.topApp);
mRunningCache.remove(id);
- final HardwareBuffer buffer = entry.snapshot.getHardwareBuffer();
- if (buffer != null) {
- buffer.close();
- }
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 1353ff0..73aa307 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -978,7 +978,6 @@
}
effectiveUid = info.applicationInfo.uid;
mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
- stringName = null;
if (info.targetActivity == null) {
if (_intent != null) {
@@ -1045,6 +1044,7 @@
updateTaskDescription();
}
mSupportsPictureInPicture = info.supportsPictureInPicture();
+ stringName = null;
// Re-adding the task to Recents once updated
if (inRecents) {
@@ -3751,9 +3751,11 @@
// Boost the adjacent TaskFragment for dimmer if needed.
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && taskFragment.isEmbedded()) {
+ taskFragment.mDimmerSurfaceBoosted = false;
final TaskFragment adjacentTf = taskFragment.getAdjacentTaskFragment();
if (adjacentTf != null && adjacentTf.shouldBoostDimmer()) {
adjacentTf.assignLayer(t, layer++);
+ adjacentTf.mDimmerSurfaceBoosted = true;
}
}
@@ -4946,13 +4948,6 @@
}
}
- void minimalResumeActivityLocked(ActivityRecord r) {
- ProtoLog.v(WM_DEBUG_STATES, "Moving to RESUMED: %s (starting new instance) "
- + "callers=%s", r, Debug.getCallers(5));
- r.setState(RESUMED, "minimalResumeActivityLocked");
- r.completeResumeLocked();
- }
-
void checkReadyForSleep() {
if (shouldSleepActivities() && goToSleepIfPossible(false /* shuttingDown */)) {
mTaskSupervisor.checkReadyForSleepLocked(true /* allowDelay */);
@@ -5861,7 +5856,7 @@
}
mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */,
- mDisplayContent.mDisplayId, false /* deferResume */);
+ mDisplayContent, false /* deferResume */);
} finally {
if (mTransitionController.isShellTransitionsEnabled()) {
mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 85d81c4..597e901 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -216,6 +216,9 @@
Dimmer mDimmer = Dimmer.DIMMER_REFACTOR
? new SmoothDimmer(this) : new LegacyDimmer(this);
+ /** {@code true} if the dimmer surface is boosted. {@code false} otherwise. */
+ boolean mDimmerSurfaceBoosted;
+
/** Apply the dim layer on the embedded TaskFragment. */
static final int EMBEDDED_DIM_AREA_TASK_FRAGMENT = 0;
@@ -1533,10 +1536,6 @@
next.setState(RESUMED, "resumeTopActivity");
- // Have the window manager re-evaluate the orientation of
- // the screen based on the new activity order.
- boolean notUpdated = true;
-
// Activity should also be visible if set mLaunchTaskBehind to true (see
// ActivityRecord#shouldBeVisibleIgnoringKeyguard()).
if (shouldBeVisible(next)) {
@@ -1548,28 +1547,15 @@
// result of invisible window resize.
// TODO: Remove this once visibilities are set correctly immediately when
// starting an activity.
- notUpdated = !mRootWindowContainer.ensureVisibilityAndConfig(next, getDisplayId(),
+ final int originalRelaunchingCount = next.mPendingRelaunchCount;
+ mRootWindowContainer.ensureVisibilityAndConfig(next, mDisplayContent,
false /* deferResume */);
- }
-
- if (notUpdated) {
- // The configuration update wasn't able to keep the existing
- // instance of the activity, and instead started a new one.
- // We should be all done, but let's just make sure our activity
- // is still at the top and schedule another run if something
- // weird happened.
- ActivityRecord nextNext = topRunningActivity();
- ProtoLog.i(WM_DEBUG_STATES, "Activity config changed during resume: "
- + "%s, new next: %s", next, nextNext);
- if (nextNext != next) {
- // Do over!
- mTaskSupervisor.scheduleResumeTopActivities();
+ if (next.mPendingRelaunchCount > originalRelaunchingCount) {
+ // The activity is scheduled to relaunch, then ResumeActivityItem will be also
+ // included (see ActivityRecord#relaunchActivityLocked) if it should resume.
+ next.completeResumeLocked();
+ return true;
}
- if (!next.isVisibleRequested() || next.mAppStopped) {
- next.setVisibility(true);
- }
- next.completeResumeLocked();
- return true;
}
try {
@@ -1652,17 +1638,7 @@
return true;
}
- // From this point on, if something goes wrong there is no way
- // to recover the activity.
- try {
- next.completeResumeLocked();
- } catch (Exception e) {
- // If any exception gets thrown, toss away this
- // activity and try the next one.
- Slog.w(TAG, "Exception thrown during resume of " + next, e);
- next.finishIfPossible("resume-exception", true /* oomAdj */);
- return true;
- }
+ next.completeResumeLocked();
} else {
// Whoops, need to restart this activity!
if (!next.hasBeenLaunched) {
@@ -1881,7 +1857,7 @@
mAtmService.getLifecycleManager().scheduleTransactionItem(prev.app.getThread(),
PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
- prev.configChangeFlags, pauseImmediately, autoEnteringPip));
+ pauseImmediately, autoEnteringPip));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 7fc61e1..a84a99a 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -573,7 +573,7 @@
// Capture the animation surface control for activity's main window
static class StartingWindowAnimationAdaptor implements AnimationAdapter {
- SurfaceControl mAnimationLeash;
+
@Override
public boolean getShowWallpaper() {
return false;
@@ -582,14 +582,10 @@
@Override
public void startAnimation(SurfaceControl animationLeash, SurfaceControl.Transaction t,
int type, @NonNull SurfaceAnimator.OnAnimationFinishedCallback finishCallback) {
- mAnimationLeash = animationLeash;
}
@Override
public void onAnimationCancelled(SurfaceControl animationLeash) {
- if (mAnimationLeash == animationLeash) {
- mAnimationLeash = null;
- }
}
@Override
@@ -604,9 +600,6 @@
@Override
public void dump(PrintWriter pw, String prefix) {
- pw.print(prefix + "StartingWindowAnimationAdaptor mCapturedLeash=");
- pw.print(mAnimationLeash);
- pw.println();
}
@Override
@@ -616,16 +609,16 @@
static SurfaceControl applyStartingWindowAnimation(WindowState window) {
final SurfaceControl.Transaction t = window.getPendingTransaction();
- final Rect mainFrame = window.getRelativeFrame();
final StartingWindowAnimationAdaptor adaptor = new StartingWindowAnimationAdaptor();
window.startAnimation(t, adaptor, false, ANIMATION_TYPE_STARTING_REVEAL);
- if (adaptor.mAnimationLeash == null) {
+ final SurfaceControl leash = window.getAnimationLeash();
+ if (leash == null) {
Slog.e(TAG, "Cannot start starting window animation, the window " + window
+ " was removed");
return null;
}
- t.setPosition(adaptor.mAnimationLeash, mainFrame.left, mainFrame.top);
- return adaptor.mAnimationLeash;
+ t.setPosition(leash, window.mSurfacePosition.x, window.mSurfacePosition.y);
+ return leash;
}
boolean addStartingWindow(Task task, ActivityRecord activity, int launchTheme,
@@ -696,7 +689,9 @@
removalInfo.roundedCornerRadius =
topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
- removalInfo.mainFrame = mainWindow.getRelativeFrame();
+ removalInfo.mainFrame = new Rect(mainWindow.getFrame());
+ removalInfo.mainFrame.offsetTo(mainWindow.mSurfacePosition.x,
+ mainWindow.mSurfacePosition.y);
}
}
try {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 594043d..9b19a70 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -349,7 +349,7 @@
final Rect lastWallpaperBounds = wallpaperWin.getParentFrame();
int screenWidth = lastWallpaperBounds.width();
int screenHeight = lastWallpaperBounds.height();
- float screenRatio = ((float) screenWidth) / screenHeight;
+ float screenRatio = (float) screenWidth / screenHeight;
Point screenSize = new Point(screenWidth, screenHeight);
WallpaperWindowToken token = wallpaperWin.mToken.asWallpaperToken();
@@ -399,20 +399,32 @@
Point bitmapSize = new Point(
wallpaperWin.mRequestedWidth, wallpaperWin.mRequestedHeight);
SparseArray<Rect> cropHints = token.getCropHints();
- wallpaperFrame = mWallpaperCropUtils.getCrop(
- screenSize, bitmapSize, cropHints, wallpaperWin.isRtl());
+ wallpaperFrame = bitmapSize.x <= 0 || bitmapSize.y <= 0 ? wallpaperWin.getFrame()
+ : mWallpaperCropUtils.getCrop(screenSize, bitmapSize, cropHints,
+ wallpaperWin.isRtl());
+ int frameWidth = wallpaperFrame.width();
+ int frameHeight = wallpaperFrame.height();
+ float frameRatio = (float) frameWidth / frameHeight;
- cropZoom = wallpaperFrame.isEmpty() ? 1f
- : ((float) screenHeight) / wallpaperFrame.height() / wallpaperWin.mVScale;
+ // If the crop is proportionally wider/taller than the screen, scale it so that its
+ // height/width matches the screen height/width, and use the additional width/height
+ // for parallax (respectively).
+ boolean scaleHeight = frameRatio >= screenRatio;
+ cropZoom = wallpaperFrame.isEmpty() ? 1f : scaleHeight
+ ? (float) screenHeight / frameHeight / wallpaperWin.mVScale
+ : (float) screenWidth / frameWidth / wallpaperWin.mHScale;
- // A positive x / y offset shifts the wallpaper to the right / bottom respectively.
- cropOffsetX = -wallpaperFrame.left
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() * screenRatio / 2f);
- cropOffsetY = -wallpaperFrame.top
- + (int) ((cropZoom - 1f) * wallpaperFrame.height() / 2f);
+ // The dimensions of the frame, without the additional width or height for parallax.
+ float w = scaleHeight ? frameHeight * screenRatio : frameWidth;
+ float h = scaleHeight ? frameHeight : frameWidth / screenRatio;
- diffWidth = (int) (wallpaperFrame.width() * wallpaperWin.mHScale) - screenWidth;
- diffHeight = (int) (wallpaperFrame.height() * wallpaperWin.mVScale) - screenHeight;
+ // Note: a positive x/y offset shifts the wallpaper to the right/bottom respectively.
+ cropOffsetX = -wallpaperFrame.left + (int) ((cropZoom - 1f) * w / 2f);
+ cropOffsetY = -wallpaperFrame.top + (int) ((cropZoom - 1f) * h / 2f);
+
+ // Available width or height for parallax
+ diffWidth = (int) ((frameWidth - w) * wallpaperWin.mHScale);
+ diffHeight = (int) ((frameHeight - h) * wallpaperWin.mVScale);
} else {
wallpaperFrame = wallpaperWin.getFrame();
cropZoom = 1f;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ae5a5cb..a055db2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2109,7 +2109,15 @@
+ ", touchableRegion=" + w.mGivenTouchableRegion + " -> " + touchableRegion
+ ", touchableInsets " + w.mTouchableInsets + " -> " + touchableInsets);
if (w != null) {
+ final boolean wasGivenInsetsPending = w.mGivenInsetsPending;
w.mGivenInsetsPending = false;
+ if ((!wasGivenInsetsPending || !w.hasInsetsSourceProvider())
+ && w.mTouchableInsets == touchableInsets
+ && w.mGivenContentInsets.equals(contentInsets)
+ && w.mGivenVisibleInsets.equals(visibleInsets)
+ && w.mGivenTouchableRegion.equals(touchableRegion)) {
+ return;
+ }
w.mGivenContentInsets.set(contentInsets);
w.mGivenVisibleInsets.set(visibleInsets);
w.mGivenTouchableRegion.set(touchableRegion);
@@ -9214,6 +9222,11 @@
return false;
}
+ if (taskFragment.mDimmerSurfaceBoosted) {
+ // Skip if the TaskFragment currently has dimmer surface boosted.
+ return false;
+ }
+
final ActivityRecord topActivity =
taskFragment.getTask().topRunningActivity(true /* focusableOnly */);
if (topActivity == null || topActivity == focusedWindow.mActivityRecord) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 0b29f96..a6db310f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -49,6 +49,7 @@
import com.android.internal.os.ByteTransferPipe;
import com.android.internal.protolog.LegacyProtoLogImpl;
+import com.android.internal.protolog.PerfettoProtoLogImpl;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.IoThread;
@@ -111,8 +112,13 @@
case "logging":
IProtoLog instance = ProtoLog.getSingleInstance();
int result = 0;
- if (instance instanceof LegacyProtoLogImpl) {
- result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+ if (instance instanceof LegacyProtoLogImpl
+ || instance instanceof PerfettoProtoLogImpl) {
+ if (instance instanceof LegacyProtoLogImpl) {
+ result = ((LegacyProtoLogImpl) instance).onShellCommand(this);
+ } else {
+ result = ((PerfettoProtoLogImpl) instance).onShellCommand(this);
+ }
if (result != 0) {
pw.println("Not handled, please use "
+ "`adb shell dumpsys activity service SystemUIService "
@@ -120,8 +126,7 @@
}
} else {
result = -1;
- pw.println("Command not supported. "
- + "Only supported when using legacy ProtoLog.");
+ pw.println("ProtoLog impl doesn't support handling commands");
}
return result;
case "user-rotation":
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 18ac0e7..46bac16 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -695,7 +695,8 @@
*/
private boolean mDrawnStateEvaluated;
- private final Point mSurfacePosition = new Point();
+ /** The surface position relative to the parent container. */
+ final Point mSurfacePosition = new Point();
/**
* A region inside of this window to be excluded from touch.
@@ -1342,7 +1343,7 @@
// This window doesn't provide any insets.
return;
}
- if (mGivenInsetsPending && mAttrs.providedInsets == null) {
+ if (mGivenInsetsPending) {
// The given insets are pending, and they are not reliable for now. The source frame
// should be updated after the new given insets are sent to window manager.
return;
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 6d5fc80..b0e71bd 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -109,6 +109,9 @@
return;
}
synchronized (mEnabledLock) {
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).startProtoLog(pw);
+ }
logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
mBuffer.resetBuffer();
mEnabled = mEnabledLockFree = true;
@@ -136,6 +139,9 @@
writeTraceToFileLocked();
logAndPrintln(pw, "Trace written to " + mTraceFile + ".");
}
+ if (!android.tracing.Flags.perfettoProtologTracing()) {
+ ((LegacyProtoLogImpl) ProtoLog.getSingleInstance()).stopProtoLog(pw, true);
+ }
}
/**
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index ccd9bd0..b2bdaa3 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -24,16 +24,17 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
+#include <powermanager/PowerHintSessionWrapper.h>
#include <utils/Log.h>
#include <unordered_map>
#include "jni.h"
-using aidl::android::hardware::power::IPowerHintSession;
using aidl::android::hardware::power::SessionHint;
using aidl::android::hardware::power::SessionMode;
using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHintSessionWrapper;
using android::base::StringPrintf;
@@ -49,7 +50,7 @@
} gWorkDurationInfo;
static power::PowerHalController gPowerHalController;
-static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::unordered_map<jlong, std::shared_ptr<PowerHintSessionWrapper>> gSessionMap;
static std::mutex gSessionMapLock;
static int64_t getHintSessionPreferredRate() {
@@ -76,45 +77,45 @@
}
static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->pause();
}
static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->resume();
}
static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->close();
std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
gSessionMap.erase(session_ptr);
}
static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->updateTargetWorkDuration(targetDurationNanos);
}
static void reportActualWorkDuration(int64_t session_ptr,
const std::vector<WorkDuration>& actualDurations) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->reportActualWorkDuration(actualDurations);
}
static void sendHint(int64_t session_ptr, SessionHint hint) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->sendHint(hint);
}
static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setThreads(threadIds);
}
static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
- auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr);
appSession->setMode(mode, enabled);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 027337f..f5e1e41 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -113,8 +113,8 @@
/** Creates intent that is ot be invoked to cancel an in-progress UI session. */
public Intent createCancelIntent(IBinder requestId, String packageName) {
- return IntentFactory.createCancelUiIntent(requestId, /*shouldShowCancellationUi=*/ true,
- packageName);
+ return IntentFactory.createCancelUiIntent(mContext, requestId,
+ /*shouldShowCancellationUi=*/ true, packageName);
}
/**
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 12f4407..6aeb4fd5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -225,7 +225,7 @@
synchronized (mLock) {
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (!handleAdminPolicySizeLimit(localPolicyState, enforcingAdmin, value,
policyDefinition, userId)) {
return;
@@ -350,7 +350,7 @@
}
PolicyState<V> localPolicyState = getLocalPolicyStateLocked(policyDefinition, userId);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
decreasePolicySizeForAdmin(localPolicyState, enforcingAdmin);
}
@@ -496,7 +496,7 @@
synchronized (mLock) {
PolicyState<V> globalPolicyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (!handleAdminPolicySizeLimit(globalPolicyState, enforcingAdmin, value,
policyDefinition, UserHandle.USER_ALL)) {
return;
@@ -568,7 +568,7 @@
synchronized (mLock) {
PolicyState<V> policyState = getGlobalPolicyStateLocked(policyDefinition);
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
decreasePolicySizeForAdmin(policyState, enforcingAdmin);
}
@@ -1892,7 +1892,7 @@
private void writeEnforcingAdminSizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (Flags.devicePolicySizeTrackingEnabled() && false) {
+ if (Flags.devicePolicySizeTrackingInternalEnabled()) {
if (mAdminPolicySize != null) {
for (int i = 0; i < mAdminPolicySize.size(); i++) {
int userId = mAdminPolicySize.keyAt(i);
@@ -1916,7 +1916,7 @@
private void writeMaxPolicySizeInner(TypedXmlSerializer serializer)
throws IOException {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
serializer.startTag(/* namespace= */ null, TAG_MAX_POLICY_SIZE_LIMIT);
@@ -2081,7 +2081,7 @@
private void readMaxPolicySizeInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
mPolicySizeLimit = parser.getAttributeInt(/* namespace= */ null, ATTR_POLICY_SUM_SIZE);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 0f97f4a..c37946b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -27,6 +27,7 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_AUTOFILL;
@@ -83,7 +84,6 @@
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -196,11 +196,11 @@
import static android.app.admin.DevicePolicyManager.STATUS_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_DEVICE_OWNER;
import static android.app.admin.DevicePolicyManager.STATUS_HAS_PAIRED;
+import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_SYSTEM_USER_MODE_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_MANAGED_USERS_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.STATUS_NONSYSTEM_USER_EXISTS;
import static android.app.admin.DevicePolicyManager.STATUS_NOT_SYSTEM_USER;
-import static android.app.admin.DevicePolicyManager.STATUS_HEADLESS_ONLY_SYSTEM_USER;
import static android.app.admin.DevicePolicyManager.STATUS_OK;
import static android.app.admin.DevicePolicyManager.STATUS_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS;
import static android.app.admin.DevicePolicyManager.STATUS_SYSTEM_USER;
@@ -12062,7 +12062,7 @@
}
if (packageList != null) {
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String pkg : packageList) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -13771,7 +13771,7 @@
return;
}
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
PolicySizeVerifier.enforceMaxStringLength(accountType, "account type");
}
@@ -14385,7 +14385,7 @@
public void setLockTaskPackages(ComponentName who, String callerPackageName, String[] packages)
throws SecurityException {
Objects.requireNonNull(packages, "packages is null");
- if (!Flags.devicePolicySizeTrackingEnabled()) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
for (String pkg : packages) {
PolicySizeVerifier.enforceMaxPackageNameLength(pkg);
}
@@ -23389,7 +23389,7 @@
DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
}
- private boolean isUnicornFlagEnabled() {
+ static boolean isUnicornFlagEnabled() {
return false;
}
@@ -24235,7 +24235,7 @@
@Override
public void setMaxPolicyStorageLimit(String callerPackageName, int storageLimit) {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
@@ -24247,7 +24247,7 @@
@Override
public int getMaxPolicyStorageLimit(String callerPackageName) {
- if (!Flags.devicePolicySizeTrackingEnabled() || true) {
+ if (!Flags.devicePolicySizeTrackingInternalEnabled()) {
return -1;
}
CallerIdentity caller = getCallerIdentity(callerPackageName);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index c108deaf..a7adc5b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -66,6 +66,10 @@
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
+ return true;
+ }
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
@@ -79,6 +83,10 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
+ if (!DevicePolicyManagerService.isUnicornFlagEnabled()) {
+ Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
+ return true;
+ }
return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index e19f08c..9d95c5b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2774,9 +2774,12 @@
t.traceEnd();
// OnDevicePersonalizationSystemService
- t.traceBegin("StartOnDevicePersonalizationSystemService");
- mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
- t.traceEnd();
+ if (!com.android.server.flags.Flags.enableOdpFeatureGuard()
+ || SystemProperties.getBoolean("ro.system_settings.service.odp_enabled", true)) {
+ t.traceBegin("StartOnDevicePersonalizationSystemService");
+ mSystemServiceManager.startService(ON_DEVICE_PERSONALIZATION_SYSTEM_SERVICE_CLASS);
+ t.traceEnd();
+ }
// Profiling
if (android.server.Flags.telemetryApisService()) {
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index fc2eb26..c0d988d 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -20,27 +20,63 @@
import android.companion.virtual.VirtualDeviceManager
import android.os.Handler
import android.os.UserHandle
+import android.permission.flags.Flags
import android.util.ArrayMap
import android.util.ArraySet
+import android.util.LongSparseArray
+import android.util.Slog
+import android.util.SparseArray
import android.util.SparseBooleanArray
import android.util.SparseIntArray
import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.util.IntPair
import com.android.server.appop.AppOpsCheckingServiceInterface
import com.android.server.appop.AppOpsCheckingServiceInterface.AppOpsModeChangedListener
import com.android.server.permission.access.AccessCheckingService
import com.android.server.permission.access.AppOpUri
+import com.android.server.permission.access.DevicePermissionUri
+import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.PackageUri
+import com.android.server.permission.access.PermissionUri
import com.android.server.permission.access.UidUri
+import com.android.server.permission.access.appop.AppOpModes.MODE_ALLOWED
+import com.android.server.permission.access.appop.AppOpModes.MODE_FOREGROUND
+import com.android.server.permission.access.appop.AppOpModes.MODE_IGNORED
import com.android.server.permission.access.collection.forEachIndexed
import com.android.server.permission.access.collection.set
+import com.android.server.permission.access.permission.AppIdPermissionPolicy
+import com.android.server.permission.access.permission.DevicePermissionPolicy
+import com.android.server.permission.access.permission.PermissionFlags
+import com.android.server.permission.access.permission.PermissionService
class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingServiceInterface {
private val packagePolicy =
service.getSchemePolicy(PackageUri.SCHEME, AppOpUri.SCHEME) as PackageAppOpPolicy
private val appIdPolicy =
service.getSchemePolicy(UidUri.SCHEME, AppOpUri.SCHEME) as AppIdAppOpPolicy
+ private val permissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
+ private val devicePermissionPolicy =
+ service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
private val context = service.context
+
+ // Maps appop code to its runtime permission
+ private val runtimeAppOpToPermissionNames = SparseArray<String>()
+
+ // Maps runtime permission to its appop codes
+ private val runtimePermissionNameToAppOp = ArrayMap<String, Int>()
+
+ private var foregroundableOps = SparseBooleanArray()
+
+ /* Maps foreground permissions to their background permission. Background permissions aren't
+ required to be runtime */
+ private val foregroundToBackgroundPermissionName = ArrayMap<String, String>()
+
+ /* Maps background permissions to their foreground permissions. Background permissions aren't
+ required to be runtime */
+ private val backgroundToForegroundPermissionNames = ArrayMap<String, ArraySet<String>>()
+
private lateinit var handler: Handler
@Volatile private var listeners = ArraySet<AppOpsModeChangedListener>()
@@ -69,11 +105,60 @@
}
override fun systemReady() {
- // Not implemented because upgrades are handled automatically.
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ createPermissionAppOpMapping()
+ val permissionListener = OnPermissionFlagsChangedListener()
+ permissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ devicePermissionPolicy.addOnPermissionFlagsChangedListener(permissionListener)
+ }
+ }
+
+ private fun createPermissionAppOpMapping() {
+ val permissions = service.getState { with(permissionPolicy) { getPermissions() } }
+
+ for (appOpCode in 0 until AppOpsManager._NUM_OP) {
+ AppOpsManager.opToPermission(appOpCode)?.let { permissionName ->
+ // Multiple ops might map to a single permission but only one is considered the
+ // runtime appop calculations.
+ if (appOpCode == AppOpsManager.permissionToOpCode(permissionName)) {
+ val permission = permissions[permissionName]!!
+ if (permission.isRuntime) {
+ runtimePermissionNameToAppOp[permissionName] = appOpCode
+ runtimeAppOpToPermissionNames[appOpCode] = permissionName
+ permission.permissionInfo.backgroundPermission?.let {
+ backgroundPermissionName ->
+ // Note: background permission may not be runtime,
+ // e.g. microphone/camera.
+ foregroundableOps[appOpCode] = true
+ foregroundToBackgroundPermissionName[permissionName] =
+ backgroundPermissionName
+ backgroundToForegroundPermissionNames
+ .getOrPut(backgroundPermissionName, ::ArraySet)
+ .add(permissionName)
+ }
+ }
+ }
+ }
+ }
}
override fun getNonDefaultUidModes(uid: Int, persistentDeviceId: String): SparseIntArray {
- return opNameMapToOpSparseArray(getUidModes(uid))
+ val appId = UserHandle.getAppId(uid)
+ val userId = UserHandle.getUserId(uid)
+ service.getState {
+ val modes =
+ with(appIdPolicy) { opNameMapToOpSparseArray(getAppOpModes(appId, userId)?.map) }
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ runtimePermissionNameToAppOp.forEachIndexed { _, permissionName, appOpCode ->
+ val mode = getUidModeFromPermissionState(appId, userId, permissionName)
+ if (mode != AppOpsManager.opToDefaultMode(appOpCode)) {
+ modes[appOpCode] = mode
+ }
+ }
+ }
+
+ return modes
+ }
}
override fun getNonDefaultPackageModes(packageName: String, userId: Int): SparseIntArray {
@@ -84,7 +169,13 @@
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
val opName = AppOpsManager.opToPublicName(op)
- return service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ val permissionName = runtimeAppOpToPermissionNames[op]
+
+ return if (!Flags.runtimePermissionAppopsMappingEnabled() || permissionName == null) {
+ service.getState { with(appIdPolicy) { getAppOpMode(appId, userId, opName) } }
+ } else {
+ service.getState { getUidModeFromPermissionState(appId, userId, permissionName) }
+ }
}
private fun getUidModes(uid: Int): ArrayMap<String, Int>? {
@@ -93,13 +184,66 @@
return service.getState { with(appIdPolicy) { getAppOpModes(appId, userId) } }?.map
}
- override fun setUidMode(uid: Int, persistentDeviceId: String, op: Int, mode: Int): Boolean {
+ private fun GetStateScope.getUidModeFromPermissionState(
+ appId: Int,
+ userId: Int,
+ permissionName: String
+ ): Int {
+ val permissionFlags =
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ val backgroundPermissionName = foregroundToBackgroundPermissionName[permissionName]
+ val backgroundPermissionFlags =
+ if (backgroundPermissionName != null) {
+ with(permissionPolicy) {
+ getPermissionFlags(appId, userId, backgroundPermissionName)
+ }
+ } else {
+ PermissionFlags.RUNTIME_GRANTED
+ }
+ val result = evaluateModeFromPermissionFlags(permissionFlags, backgroundPermissionFlags)
+ if (result != MODE_IGNORED) {
+ return result
+ }
+
+ val fullerPermissionName =
+ PermissionService.getFullerPermission(permissionName) ?: return result
+ return getUidModeFromPermissionState(appId, userId, fullerPermissionName)
+ }
+
+ private fun evaluateModeFromPermissionFlags(
+ foregroundFlags: Int,
+ backgroundFlags: Int = PermissionFlags.RUNTIME_GRANTED
+ ): Int =
+ if (PermissionFlags.isAppOpGranted(foregroundFlags)) {
+ if (PermissionFlags.isAppOpGranted(backgroundFlags)) {
+ MODE_ALLOWED
+ } else {
+ MODE_FOREGROUND
+ }
+ } else {
+ MODE_IGNORED
+ }
+
+ override fun setUidMode(uid: Int, persistentDeviceId: String, code: Int, mode: Int): Boolean {
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() && code in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ " code = ${AppOpsManager.opToName(code)}," +
+ " mode = ${AppOpsManager.modeToName(mode)}",
+ RuntimeException()
+ )
+ return false
+ }
+
val appId = UserHandle.getAppId(uid)
val userId = UserHandle.getUserId(uid)
- val opName = AppOpsManager.opToPublicName(op)
- var wasChanged = false
+ val appOpName = AppOpsManager.opToPublicName(code)
+ var wasChanged: Boolean
service.mutateState {
- wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, opName, mode) }
+ wasChanged = with(appIdPolicy) { setAppOpMode(appId, userId, appOpName, mode) }
}
return wasChanged
}
@@ -114,10 +258,23 @@
private fun getPackageModes(packageName: String, userId: Int): ArrayMap<String, Int>? =
service.getState { with(packagePolicy) { getAppOpModes(packageName, userId) } }?.map
- override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
- val opName = AppOpsManager.opToPublicName(op)
+ override fun setPackageMode(packageName: String, appOpCode: Int, mode: Int, userId: Int) {
+ val appOpName = AppOpsManager.opToPublicName(appOpCode)
+
+ if (
+ Flags.runtimePermissionAppopsMappingEnabled() &&
+ appOpCode in runtimeAppOpToPermissionNames
+ ) {
+ Slog.w(
+ LOG_TAG,
+ "(packageName=$packageName, userId=$userId)'s appop state" +
+ " for runtime op $appOpName should not be set directly.",
+ RuntimeException()
+ )
+ return
+ }
service.mutateState {
- with(packagePolicy) { setAppOpMode(packageName, userId, opName, mode) }
+ with(packagePolicy) { setAppOpMode(packageName, userId, appOpName, mode) }
}
}
@@ -128,7 +285,7 @@
}
override fun removePackage(packageName: String, userId: Int): Boolean {
- var wasChanged = false
+ var wasChanged: Boolean
service.mutateState {
wasChanged = with(packagePolicy) { removeAppOpModes(packageName, userId) }
}
@@ -158,6 +315,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getUidMode(uid, persistentDeviceId, op) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -168,6 +332,13 @@
this[AppOpsManager.strOpToOp(op)] = true
}
}
+ if (Flags.runtimePermissionAppopsMappingEnabled()) {
+ foregroundableOps.forEachIndexed { _, op, _ ->
+ if (getPackageMode(packageName, op, userId) == AppOpsManager.MODE_FOREGROUND) {
+ this[op] = true
+ }
+ }
+ }
}
}
@@ -189,9 +360,10 @@
}
}
- inner class OnAppIdAppOpModeChangedListener : AppIdAppOpPolicy.OnAppOpModeChangedListener() {
+ private inner class OnAppIdAppOpModeChangedListener :
+ AppIdAppOpPolicy.OnAppOpModeChangedListener() {
// (uid, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Pair<Int, Int>, Int>()
+ private val pendingChanges = LongSparseArray<Int>()
override fun onAppOpModeChanged(
appId: Int,
@@ -202,7 +374,7 @@
) {
val uid = UserHandle.getUid(userId, appId)
val appOpCode = AppOpsManager.strOpToOp(appOpName)
- val key = Pair(uid, appOpCode)
+ val key = IntPair.of(uid, appOpCode)
pendingChanges[key] = newMode
}
@@ -211,13 +383,15 @@
val listenersLocal = listeners
pendingChanges.forEachIndexed { _, key, mode ->
listenersLocal.forEachIndexed { _, listener ->
- val uid = key.first
- val appOpCode = key.second
+ val uid = IntPair.first(key)
+ val appOpCode = IntPair.second(key)
- listener.onUidModeChanged(uid,
+ listener.onUidModeChanged(
+ uid,
appOpCode,
mode,
- VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
+ )
}
}
@@ -228,7 +402,7 @@
private inner class OnPackageAppOpModeChangedListener :
PackageAppOpPolicy.OnAppOpModeChangedListener() {
// (packageName, userId, appOpCode) -> newMode
- val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
+ private val pendingChanges = ArrayMap<Triple<String, Int, Int>, Int>()
override fun onAppOpModeChanged(
packageName: String,
@@ -258,4 +432,130 @@
pendingChanges.clear()
}
}
+
+ private inner class OnPermissionFlagsChangedListener :
+ AppIdPermissionPolicy.OnPermissionFlagsChangedListener,
+ DevicePermissionPolicy.OnDevicePermissionFlagsChangedListener {
+ // (uid, deviceId, appOpCode) -> newMode
+ private val pendingChanges = ArrayMap<Triple<Int, String, Int>, Int>()
+
+ override fun onPermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ onDevicePermissionFlagsChanged(
+ appId,
+ userId,
+ VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT,
+ permissionName,
+ oldFlags,
+ newFlags
+ )
+ }
+
+ override fun onDevicePermissionFlagsChanged(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ permissionName: String,
+ oldFlags: Int,
+ newFlags: Int
+ ) {
+ backgroundToForegroundPermissionNames[permissionName]?.let { foregroundPermissions ->
+ // This is a background permission; there may be multiple foreground permissions
+ // affected.
+ foregroundPermissions.forEachIndexed { _, foregroundPermissionName ->
+ runtimePermissionNameToAppOp[foregroundPermissionName]?.let { appOpCode ->
+ val foregroundPermissionFlags =
+ getPermissionFlags(appId, userId, foregroundPermissionName)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ foregroundPermissionFlags,
+ oldFlags,
+ foregroundPermissionFlags,
+ newFlags
+ )
+ }
+ }
+ }
+ ?: foregroundToBackgroundPermissionName[permissionName]?.let { backgroundPermission
+ ->
+ runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ val backgroundPermissionFlags =
+ getPermissionFlags(appId, userId, backgroundPermission)
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ backgroundPermissionFlags,
+ newFlags,
+ backgroundPermissionFlags
+ )
+ }
+ }
+ ?: runtimePermissionNameToAppOp[permissionName]?.let { appOpCode ->
+ addPendingChangedModeIfNeeded(
+ appId,
+ userId,
+ deviceId,
+ appOpCode,
+ oldFlags,
+ PermissionFlags.RUNTIME_GRANTED,
+ newFlags,
+ PermissionFlags.RUNTIME_GRANTED
+ )
+ }
+ }
+
+ private fun getPermissionFlags(appId: Int, userId: Int, permissionName: String): Int =
+ service.getState {
+ with(permissionPolicy) { getPermissionFlags(appId, userId, permissionName) }
+ }
+
+ private fun addPendingChangedModeIfNeeded(
+ appId: Int,
+ userId: Int,
+ deviceId: String,
+ appOpCode: Int,
+ oldForegroundFlags: Int,
+ oldBackgroundFlags: Int,
+ newForegroundFlags: Int,
+ newBackgroundFlags: Int,
+ ) {
+ val oldMode = evaluateModeFromPermissionFlags(oldForegroundFlags, oldBackgroundFlags)
+ val newMode = evaluateModeFromPermissionFlags(newForegroundFlags, newBackgroundFlags)
+
+ if (oldMode != newMode) {
+ val uid = UserHandle.getUid(userId, appId)
+ pendingChanges[Triple(uid, deviceId, appOpCode)] = newMode
+ }
+ }
+
+ override fun onStateMutated() {
+ val listenersLocal = listeners
+ pendingChanges.forEachIndexed { _, key, mode ->
+ listenersLocal.forEachIndexed { _, listener ->
+ val uid = key.first
+ val deviceId = key.second
+ val appOpCode = key.third
+
+ listener.onUidModeChanged(uid, appOpCode, mode, deviceId)
+ }
+ }
+
+ pendingChanges.clear()
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = AppOpService::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
new file mode 100644
index 0000000..827dd0e
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/LongSparseArrayExtensions.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.permission.access.collection
+
+import android.util.LongSparseArray
+
+inline fun <T> LongSparseArray<T>.allIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.anyIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun <T> LongSparseArray<T>.forEachIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.forEachReversedIndexed(action: (Int, Long, T) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun <T> LongSparseArray<T>.getOrPut(key: Long, defaultValue: () -> T): T {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val <T> LongSparseArray<T>.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.minusAssign(key: Long) {
+ delete(key)
+}
+
+inline fun <T> LongSparseArray<T>.noneIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun <T> LongSparseArray<T>.removeAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun <T> LongSparseArray<T>.retainAllIndexed(predicate: (Int, Long, T) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline val <T> LongSparseArray<T>.size: Int
+ get() = size()
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun <T> LongSparseArray<T>.set(key: Long, value: T) {
+ put(key, value)
+}
diff --git a/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
new file mode 100644
index 0000000..a582431
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/collection/SparseIntArrayExtensions.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.permission.access.collection
+
+import android.util.SparseIntArray
+
+inline fun SparseIntArray.allIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+inline fun SparseIntArray.anyIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return true
+ }
+ }
+ return false
+}
+
+inline fun SparseIntArray.forEachIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in 0 until size) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.forEachReversedIndexed(action: (Int, Int, Int) -> Unit) {
+ for (index in lastIndex downTo 0) {
+ action(index, keyAt(index), valueAt(index))
+ }
+}
+
+inline fun SparseIntArray.getOrPut(key: Int, defaultValue: () -> Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ valueAt(index)
+ } else {
+ defaultValue().also { put(key, it) }
+ }
+}
+
+inline val SparseIntArray.lastIndex: Int
+ get() = size - 1
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.minusAssign(key: Int) {
+ delete(key)
+}
+
+inline fun SparseIntArray.noneIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ forEachIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ return false
+ }
+ }
+ return true
+}
+
+fun SparseIntArray.remove(key: Int) {
+ delete(key)
+}
+
+fun SparseIntArray.remove(key: Int, defaultValue: Int): Int {
+ val index = indexOfKey(key)
+ return if (index >= 0) {
+ val oldValue = valueAt(index)
+ removeAt(index)
+ oldValue
+ } else {
+ defaultValue
+ }
+}
+
+inline fun SparseIntArray.removeAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+inline fun SparseIntArray.retainAllIndexed(predicate: (Int, Int, Int) -> Boolean): Boolean {
+ var isChanged = false
+ forEachReversedIndexed { index, key, value ->
+ if (!predicate(index, key, value)) {
+ removeAt(index)
+ isChanged = true
+ }
+ }
+ return isChanged
+}
+
+@Suppress("NOTHING_TO_INLINE")
+inline operator fun SparseIntArray.set(key: Int, value: Int) {
+ put(key, value)
+}
+
+inline val SparseIntArray.size: Int
+ get() = size()
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 4b086b3..67df67f 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -227,25 +227,59 @@
if (isRequestedBySystemPackage) {
return@forEach
}
- val oldFlags = getPermissionFlags(appId, userId, permissionName)
- var newFlags = oldFlags andInv PermissionFlags.UPGRADE_EXEMPT
- val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
- newFlags =
- if (permission.isHardRestricted && !isExempt) {
- newFlags or PermissionFlags.RESTRICTION_REVOKED
- } else {
- newFlags andInv PermissionFlags.RESTRICTION_REVOKED
- }
- newFlags =
- if (permission.isSoftRestricted && !isExempt) {
- newFlags or PermissionFlags.SOFT_RESTRICTED
- } else {
- newFlags andInv PermissionFlags.SOFT_RESTRICTED
- }
- setPermissionFlags(appId, userId, permissionName, newFlags)
+ updatePermissionExemptFlags(
+ appId,
+ userId,
+ permission,
+ PermissionFlags.UPGRADE_EXEMPT,
+ 0
+ )
}
}
+ fun MutateStateScope.updatePermissionExemptFlags(
+ appId: Int,
+ userId: Int,
+ permission: Permission,
+ exemptFlagMask: Int,
+ exemptFlagValues: Int
+ ) {
+ val permissionName = permission.name
+ val oldFlags = getPermissionFlags(appId, userId, permissionName)
+ var newFlags = (oldFlags andInv exemptFlagMask) or (exemptFlagValues and exemptFlagMask)
+ if (oldFlags == newFlags) {
+ return
+ }
+ val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+ if (permission.isHardRestricted && !isExempt) {
+ newFlags = newFlags or PermissionFlags.RESTRICTION_REVOKED
+ // If the permission was policy fixed as granted but it is no longer on any of the
+ // allowlists we need to clear the policy fixed flag as allowlisting trumps policy i.e.
+ // policy cannot grant a non grantable permission.
+ if (PermissionFlags.isPermissionGranted(oldFlags)) {
+ newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+ }
+ } else {
+ newFlags = newFlags andInv PermissionFlags.RESTRICTION_REVOKED
+ }
+ newFlags =
+ if (
+ permission.isSoftRestricted && !isExempt &&
+ !anyPackageInAppId(appId) {
+ permissionName in it.androidPackage!!.requestedPermissions &&
+ isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ }
+ ) {
+ newFlags or PermissionFlags.SOFT_RESTRICTED
+ } else {
+ newFlags andInv PermissionFlags.SOFT_RESTRICTED
+ }
+ if (oldFlags == newFlags) {
+ return
+ }
+ setPermissionFlags(appId, userId, permissionName, newFlags)
+ }
+
override fun MutateStateScope.onPackageUninstalled(
packageName: String,
appId: Int,
@@ -1118,7 +1152,12 @@
newFlags andInv PermissionFlags.RESTRICTION_REVOKED
}
newFlags =
- if (permission.isSoftRestricted && !isExempt) {
+ if (
+ permission.isSoftRestricted && !isExempt &&
+ !requestingPackageStates.anyIndexed { _, it ->
+ isSoftRestrictedPermissionExemptForPackage(it, permissionName)
+ }
+ ) {
newFlags or PermissionFlags.SOFT_RESTRICTED
} else {
newFlags andInv PermissionFlags.SOFT_RESTRICTED
@@ -1398,6 +1437,17 @@
}
}
+ // See also SoftRestrictedPermissionPolicy.mayGrantPermission()
+ private fun isSoftRestrictedPermissionExemptForPackage(
+ packageState: PackageState,
+ permissionName: String
+ ): Boolean =
+ when (permissionName) {
+ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE ->
+ packageState.androidPackage!!.targetSdkVersion >= Build.VERSION_CODES.Q
+ else -> false
+ }
+
private inline fun MutateStateScope.anyPackageInAppId(
appId: Int,
state: AccessState = newState,
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
index 28889de..c5c921d 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionFlags.kt
@@ -346,9 +346,18 @@
return flags.hasBits(RUNTIME_GRANTED)
}
- fun isAppOpGranted(flags: Int): Boolean =
- isPermissionGranted(flags) && !flags.hasBits(RESTRICTION_REVOKED) &&
- !flags.hasBits(APP_OP_REVOKED)
+ fun isAppOpGranted(flags: Int): Boolean {
+ if (!isPermissionGranted(flags)) {
+ return false
+ }
+ if (flags.hasAnyBit(MASK_RESTRICTED)) {
+ return false
+ }
+ if (flags.hasBits(APP_OP_REVOKED)) {
+ return false
+ }
+ return true
+ }
fun toApiFlags(flags: Int): Int {
var apiFlags = 0
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0704c8f..b32c544 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -88,7 +88,6 @@
import com.android.server.pm.PackageManagerLocal
import com.android.server.pm.UserManagerInternal
import com.android.server.pm.UserManagerService
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils
import com.android.server.pm.permission.LegacyPermission
import com.android.server.pm.permission.LegacyPermissionSettings
import com.android.server.pm.permission.LegacyPermissionState
@@ -97,7 +96,6 @@
import com.android.server.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageState
-import com.android.server.policy.SoftRestrictedPermissionPolicy
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.CompletableFuture
@@ -1006,25 +1004,14 @@
}
if (isGranted && oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)) {
- // TODO: Refactor SoftRestrictedPermissionPolicy.
- val softRestrictedPermissionPolicy =
- SoftRestrictedPermissionPolicy.forPermission(
- context,
- AndroidPackageUtils.generateAppInfoWithoutState(androidPackage),
- androidPackage,
- UserHandle.of(userId),
- permissionName
+ if (reportError) {
+ Slog.e(
+ LOG_TAG,
+ "$methodName: Cannot grant soft-restricted non-exempt permission" +
+ " $permissionName to package $packageName"
)
- if (!softRestrictedPermissionPolicy.mayGrantPermission()) {
- if (reportError) {
- Slog.e(
- LOG_TAG,
- "$methodName: Cannot grant soft-restricted non-exempt permission" +
- " $permissionName to package $packageName"
- )
- }
- return
}
+ return
}
val newFlags = PermissionFlags.updateRuntimePermissionGranted(oldFlags, isGranted)
@@ -1125,6 +1112,8 @@
)
enforceCallingOrSelfAnyPermission(
"getAllPermissionStates",
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
Manifest.permission.GET_RUNTIME_PERMISSIONS
)
@@ -1135,25 +1124,23 @@
return emptyMap()
}
- val permissionFlagsMap =
- service.getState {
+ service.getState {
+ val permissionFlags =
if (deviceId == VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT) {
with(policy) { getAllPermissionFlags(packageState.appId, userId) }
} else {
with(devicePolicy) {
getAllPermissionFlags(packageState.appId, deviceId, userId)
}
- }
+ } ?: return emptyMap()
+ val permissionStates = ArrayMap<String, PermissionState>()
+ permissionFlags.forEachIndexed { _, permissionName, flags ->
+ val granted = isPermissionGranted(packageState, userId, permissionName, deviceId)
+ val apiFlags = PermissionFlags.toApiFlags(flags)
+ permissionStates[permissionName] = PermissionState(granted, apiFlags)
}
- ?: return emptyMap()
-
- val permissionStates = ArrayMap<String, PermissionState>()
- permissionFlagsMap.forEachIndexed { _, permissionName, flags ->
- val granted = PermissionFlags.isPermissionGranted(flags)
- val apiFlags = PermissionFlags.toApiFlags(flags)
- permissionStates[permissionName] = PermissionState(granted, apiFlags)
+ return permissionStates
}
- return permissionStates
}
override fun isPermissionRevokedByPolicy(
@@ -1852,10 +1839,19 @@
allowlistedFlags: Int,
userId: Int
) {
+ var exemptMask = 0
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+ exemptMask = exemptMask or PermissionFlags.SYSTEM_EXEMPT
+ }
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+ exemptMask = exemptMask or PermissionFlags.UPGRADE_EXEMPT
+ }
+ if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+ exemptMask = exemptMask or PermissionFlags.INSTALLER_EXEMPT
+ }
+
service.mutateState {
with(policy) {
- val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
-
val permissions = getPermissions()
androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
val permission = permissions[requestedPermission]
@@ -1863,81 +1859,8 @@
return@forEachIndexed
}
- val oldFlags = permissionsFlags[requestedPermission] ?: 0
- val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
-
- var newFlags = oldFlags
- var mask = 0
- var allowlistFlagsCopy = allowlistedFlags
- while (allowlistFlagsCopy != 0) {
- val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
- allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
- when (flag) {
- PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
- mask = mask or PermissionFlags.SYSTEM_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.SYSTEM_EXEMPT
- } else {
- newFlags andInv PermissionFlags.SYSTEM_EXEMPT
- }
- }
- PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
- mask = mask or PermissionFlags.UPGRADE_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.UPGRADE_EXEMPT
- } else {
- newFlags andInv PermissionFlags.UPGRADE_EXEMPT
- }
- }
- PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
- mask = mask or PermissionFlags.INSTALLER_EXEMPT
- newFlags =
- if (permissionNames.contains(requestedPermission)) {
- newFlags or PermissionFlags.INSTALLER_EXEMPT
- } else {
- newFlags andInv PermissionFlags.INSTALLER_EXEMPT
- }
- }
- }
- }
-
- if (oldFlags == newFlags) {
- return@forEachIndexed
- }
-
- val isExempt = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
-
- // If the permission is policy fixed as granted but it is no longer
- // on any of the allowlists we need to clear the policy fixed flag
- // as allowlisting trumps policy i.e. policy cannot grant a non
- // grantable permission.
- if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
- if (!isExempt && wasGranted) {
- mask = mask or PermissionFlags.POLICY_FIXED
- newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
- }
- }
-
- newFlags =
- if (permission.isHardRestricted && !isExempt) {
- newFlags or PermissionFlags.RESTRICTION_REVOKED
- } else {
- newFlags andInv PermissionFlags.RESTRICTION_REVOKED
- }
- newFlags =
- if (permission.isSoftRestricted && !isExempt) {
- newFlags or PermissionFlags.SOFT_RESTRICTED
- } else {
- newFlags andInv PermissionFlags.SOFT_RESTRICTED
- }
- mask =
- mask or
- PermissionFlags.RESTRICTION_REVOKED or
- PermissionFlags.SOFT_RESTRICTED
-
- updatePermissionFlags(appId, userId, requestedPermission, mask, newFlags)
+ var exemptFlags = if (requestedPermission in permissionNames) exemptMask else 0
+ updatePermissionExemptFlags(appId, userId, permission, exemptMask, exemptFlags)
}
}
}
@@ -2905,5 +2828,8 @@
} else {
emptySet<String>()
}
+
+ fun getFullerPermission(permissionName: String): String? =
+ FULLER_PERMISSIONS[permissionName]
}
}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index 66e0717..c54a94e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -83,11 +83,7 @@
}
val bundle = service.getUriRelativeFilterGroups(PKG_ONE, listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.keySet()).containsExactlyElementsIn(listOf(DOMAIN_1, DOMAIN_2))
- assertThat(bundle.getParcelableArrayList(DOMAIN_1, UriRelativeFilterGroup::class.java))
- .isEmpty()
- assertThat(bundle.getParcelableArrayList(DOMAIN_2, UriRelativeFilterGroup::class.java))
- .isEmpty()
+ assertThat(bundle.keySet()).isEmpty()
val pathGroup = UriRelativeFilterGroup(UriRelativeFilterGroup.ACTION_ALLOW)
pathGroup.addUriRelativeFilter(
diff --git a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
index cde46ab..96753b6 100644
--- a/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
+++ b/services/tests/PermissionServiceMockingTests/src/com/android/server/permission/test/AppIdPermissionPolicyTest.kt
@@ -233,24 +233,6 @@
.isEqualTo(expectedNewFlags)
}
- @Test
- fun testOnPackageInstalled_restrictedPermissionsIsExempted_clearsRestrictionFlags() {
- val oldFlags = PermissionFlags.SOFT_RESTRICTED or PermissionFlags.INSTALLER_EXEMPT
- testOnPackageInstalled(
- oldFlags,
- permissionInfoFlags = PermissionInfo.FLAG_SOFT_RESTRICTED
- ) {}
- val actualFlags = getPermissionFlags(APP_ID_1, USER_ID_0, PERMISSION_NAME_0)
- val expectedNewFlags = PermissionFlags.INSTALLER_EXEMPT
- assertWithMessage(
- "After onPackageInstalled() is called for a non-system app that requests a runtime" +
- " soft restricted permission that is exempted. The actual permission flags" +
- " $actualFlags should match the expected flags $expectedNewFlags"
- )
- .that(actualFlags)
- .isEqualTo(expectedNewFlags)
- }
-
private fun testOnPackageInstalled(
oldFlags: Int,
permissionInfoFlags: Int = 0,
diff --git a/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
new file mode 100644
index 0000000..180f54e
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/Ikev2VpnProfileTest.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V6;
+import static android.net.cts.util.IkeSessionTestUtils.getTestIkeSessionParams;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ipsec.ike.IkeKeyIdIdentification;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.org.bouncycastle.x509.X509V1CertificateGenerator;
+import com.android.net.module.util.ProxyUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.security.auth.x500.X500Principal;
+
+/** Unit tests for {@link Ikev2VpnProfile.Builder}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class Ikev2VpnProfileTest {
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final String USERNAME_STRING = "username";
+ private static final String PASSWORD_STRING = "pa55w0rd";
+ private static final String EXCL_LIST = "exclList";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+ private static final int TEST_MTU = 1300;
+
+ private final ProxyInfo mProxy = ProxyInfo.buildDirectProxy(
+ SERVER_ADDR_STRING, -1, ProxyUtils.exclusionStringAsList(EXCL_LIST));
+
+ private X509Certificate mUserCert;
+ private X509Certificate mServerRootCa;
+ private PrivateKey mPrivateKey;
+
+ @Before
+ public void setUp() throws Exception {
+ mServerRootCa = generateRandomCertAndKeyPair().cert;
+
+ final CertificateAndKey userCertKey = generateRandomCertAndKeyPair();
+ mUserCert = userCertKey.cert;
+ mPrivateKey = userCertKey.key;
+ }
+
+ private Ikev2VpnProfile.Builder getBuilderWithDefaultOptions() {
+ final Ikev2VpnProfile.Builder builder =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING);
+
+ builder.setBypassable(true);
+ builder.setProxy(mProxy);
+ builder.setMaxMtu(TEST_MTU);
+ builder.setMetered(true);
+
+ return builder;
+ }
+
+ @Test
+ public void testBuildValidProfileWithOptions() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ // Check non-auth parameters correctly stored
+ assertEquals(SERVER_ADDR_STRING, profile.getServerAddr());
+ assertEquals(IDENTITY_STRING, profile.getUserIdentity());
+ assertEquals(mProxy, profile.getProxyInfo());
+ assertTrue(profile.isBypassable());
+ assertTrue(profile.isMetered());
+ assertEquals(TEST_MTU, profile.getMaxMtu());
+ assertEquals(Ikev2VpnProfile.DEFAULT_ALGORITHMS, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildUsernamePasswordProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(USERNAME_STRING, profile.getUsername());
+ assertEquals(PASSWORD_STRING, profile.getPassword());
+ assertEquals(mServerRootCa, profile.getServerRootCaCert());
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildDigitalSignatureProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertEquals(profile.getUserCert(), mUserCert);
+ assertEquals(mPrivateKey, profile.getRsaPrivateKey());
+ assertEquals(profile.getServerRootCaCert(), mServerRootCa);
+
+ assertNull(profile.getPresharedKey());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ }
+
+ @Test
+ public void testBuildPresharedKeyProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+
+ assertArrayEquals(PSK_BYTES, profile.getPresharedKey());
+
+ assertNull(profile.getServerRootCaCert());
+ assertNull(profile.getUsername());
+ assertNull(profile.getPassword());
+ assertNull(profile.getRsaPrivateKey());
+ assertNull(profile.getUserCert());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsAead() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testBuildWithAllowedAlgorithmsNormal() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+
+ List<String> allowedAlgorithms =
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.AUTH_AES_XCBC,
+ IpSecAlgorithm.AUTH_AES_CMAC,
+ IpSecAlgorithm.CRYPT_AES_CBC,
+ IpSecAlgorithm.CRYPT_AES_CTR);
+ builder.setAllowedAlgorithms(allowedAlgorithms);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertEquals(allowedAlgorithms, profile.getAllowedAlgorithms());
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsEmptyList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(new ArrayList<>());
+ fail("Expected exception due to no valid algorithm set");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInvalidList() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA256));
+ fail("Expected exception due to missing encryption");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.CRYPT_AES_CBC));
+ fail("Expected exception due to missing authentication");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAllowedAlgorithmsInsecureAlgorithm() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_MD5));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ builder.setAllowedAlgorithms(Arrays.asList(IpSecAlgorithm.AUTH_HMAC_SHA1));
+ fail("Expected exception due to insecure algorithm");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildNoAuthMethodSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.build();
+ fail("Expected exception due to lack of auth method");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildExcludeLocalRoutesSet() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthPsk(PSK_BYTES);
+ builder.setLocalRoutesExcluded(true);
+
+ final Ikev2VpnProfile profile = builder.build();
+ assertNotNull(profile);
+ assertTrue(profile.areLocalRoutesExcluded());
+
+ builder.setBypassable(false);
+ try {
+ builder.build();
+ fail("Expected exception because excludeLocalRoutes should be set only"
+ + " on the bypassable VPN");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testBuildInvalidMtu() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ try {
+ builder.setMaxMtu(500);
+ fail("Expected exception due to too-small MTU");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ private void verifyVpnProfileCommon(VpnProfile profile) {
+ assertEquals(SERVER_ADDR_STRING, profile.server);
+ assertEquals(IDENTITY_STRING, profile.ipsecIdentifier);
+ assertEquals(mProxy, profile.proxy);
+ assertTrue(profile.isBypassable);
+ assertTrue(profile.isMetered);
+ assertEquals(TEST_MTU, profile.maxMtu);
+ }
+
+ @Test
+ public void testPskConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.encodeForIpsecSecret(PSK_BYTES), profile.ipsecSecret);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecCaCert);
+ }
+
+ @Test
+ public void testUsernamePasswordConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ verifyVpnProfileCommon(profile);
+ assertEquals(USERNAME_STRING, profile.username);
+ assertEquals(PASSWORD_STRING, profile.password);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.ipsecUserCert);
+ assertEquals("", profile.ipsecSecret);
+ }
+
+ @Test
+ public void testRsaConvertToVpnProfile() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+
+ final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+ + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
+ verifyVpnProfileCommon(profile);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
+ assertEquals(
+ expectedSecret,
+ profile.ipsecSecret);
+ assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
+
+ // Check nothing else is set
+ assertEquals("", profile.username);
+ assertEquals("", profile.password);
+ }
+
+ @Test
+ public void testPskFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+ profile.ipsecCaCert = Ikev2VpnProfile.certificateToPemString(mServerRootCa);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ assertNull(result.getServerRootCaCert());
+ }
+
+ @Test
+ public void testUsernamePasswordFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.ipsecSecret = new String(PSK_BYTES);
+ profile.ipsecUserCert = Ikev2VpnProfile.certificateToPemString(mUserCert);
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getPresharedKey());
+ assertNull(result.getUserCert());
+ assertNull(result.getRsaPrivateKey());
+ }
+
+ @Test
+ public void testRsaFromVpnProfileDiscardsIrrelevantValues() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final VpnProfile profile = builder.build().toVpnProfile();
+ profile.username = USERNAME_STRING;
+ profile.password = PASSWORD_STRING;
+
+ final Ikev2VpnProfile result = Ikev2VpnProfile.fromVpnProfile(profile);
+ assertNull(result.getUsername());
+ assertNull(result.getPassword());
+ assertNull(result.getPresharedKey());
+ }
+
+ @Test
+ public void testPskConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthPsk(PSK_BYTES);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testUsernamePasswordConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testRsaConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testBuildWithIkeTunConnParamsConvertToVpnProfile() throws Exception {
+ // Special keyId that contains delimiter character of VpnProfile
+ final byte[] keyId = "foo\0bar".getBytes();
+ final IkeTunnelConnectionParams tunnelParams = new IkeTunnelConnectionParams(
+ getTestIkeSessionParams(true /* testIpv6 */, new IkeKeyIdIdentification(keyId)),
+ CHILD_PARAMS);
+ final Ikev2VpnProfile ikev2VpnProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+
+ assertEquals(VpnProfile.TYPE_IKEV2_FROM_IKE_TUN_CONN_PARAMS, vpnProfile.type);
+
+ // Username, password, server, ipsecIdentifier, ipsecCaCert, ipsecSecret, ipsecUserCert and
+ // getAllowedAlgorithms should not be set if IkeTunnelConnectionParams is set.
+ assertEquals("", vpnProfile.server);
+ assertEquals("", vpnProfile.ipsecIdentifier);
+ assertEquals("", vpnProfile.username);
+ assertEquals("", vpnProfile.password);
+ assertEquals("", vpnProfile.ipsecCaCert);
+ assertEquals("", vpnProfile.ipsecSecret);
+ assertEquals("", vpnProfile.ipsecUserCert);
+ assertEquals(0, vpnProfile.getAllowedAlgorithms().size());
+
+ // IkeTunnelConnectionParams should stay the same.
+ assertEquals(tunnelParams, vpnProfile.ikeTunConnParams);
+
+ // Convert to disk-stable format and then back to Ikev2VpnProfile should be the same.
+ final VpnProfile decodedVpnProfile =
+ VpnProfile.decode(vpnProfile.key, vpnProfile.encode());
+ final Ikev2VpnProfile convertedIkev2VpnProfile =
+ Ikev2VpnProfile.fromVpnProfile(decodedVpnProfile);
+ assertEquals(ikev2VpnProfile, convertedIkev2VpnProfile);
+ }
+
+ @Test
+ public void testConversionIsLosslessWithIkeTunConnParams() throws Exception {
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ // Config authentication related fields is not required while building with
+ // IkeTunnelConnectionParams.
+ final Ikev2VpnProfile ikeProfile = new Ikev2VpnProfile.Builder(tunnelParams).build();
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionConversionIsLossless() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAutomaticNattKeepaliveTimerEnabled(true);
+ builder.setAutomaticIpVersionSelectionEnabled(true);
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(ikeProfile, Ikev2VpnProfile.fromVpnProfile(ikeProfile.toVpnProfile()));
+ }
+
+ @Test
+ public void testAutomaticNattAndIpVersionDefaults() throws Exception {
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ final Ikev2VpnProfile ikeProfile = builder.build();
+
+ assertEquals(false, ikeProfile.isAutomaticNattKeepaliveTimerEnabled());
+ assertEquals(false, ikeProfile.isAutomaticIpVersionSelectionEnabled());
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ // Verify building without IkeTunnelConnectionParams
+ final Ikev2VpnProfile.Builder builder = getBuilderWithDefaultOptions();
+ builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
+ assertEquals(builder.build(), builder.build());
+
+ // Verify building with IkeTunnelConnectionParams
+ final IkeTunnelConnectionParams tunnelParams =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ final IkeTunnelConnectionParams tunnelParams2 =
+ new IkeTunnelConnectionParams(IKE_PARAMS_V6, CHILD_PARAMS);
+ assertEquals(new Ikev2VpnProfile.Builder(tunnelParams).build(),
+ new Ikev2VpnProfile.Builder(tunnelParams2).build());
+ }
+
+ @Test
+ public void testBuildProfileWithNullProxy() throws Exception {
+ final Ikev2VpnProfile ikev2VpnProfile =
+ new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setAuthUsernamePassword(USERNAME_STRING, PASSWORD_STRING, mServerRootCa)
+ .build();
+
+ // ProxyInfo should be null for the profile without setting ProxyInfo.
+ assertNull(ikev2VpnProfile.getProxyInfo());
+
+ // ProxyInfo should stay null after performing toVpnProfile() and fromVpnProfile()
+ final VpnProfile vpnProfile = ikev2VpnProfile.toVpnProfile();
+ assertNull(vpnProfile.proxy);
+
+ final Ikev2VpnProfile convertedIkev2VpnProfile = Ikev2VpnProfile.fromVpnProfile(vpnProfile);
+ assertNull(convertedIkev2VpnProfile.getProxyInfo());
+ }
+
+ private static class CertificateAndKey {
+ public final X509Certificate cert;
+ public final PrivateKey key;
+
+ CertificateAndKey(X509Certificate cert, PrivateKey key) {
+ this.cert = cert;
+ this.key = key;
+ }
+ }
+
+ private static CertificateAndKey generateRandomCertAndKeyPair() throws Exception {
+ final Date validityBeginDate =
+ new Date(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1L));
+ final Date validityEndDate =
+ new Date(System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1L));
+
+ // Generate a keypair
+ final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(512);
+ final KeyPair keyPair = keyPairGenerator.generateKeyPair();
+
+ final X500Principal dnName = new X500Principal("CN=test.android.com");
+ final X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
+ certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
+ certGen.setSubjectDN(dnName);
+ certGen.setIssuerDN(dnName);
+ certGen.setNotBefore(validityBeginDate);
+ certGen.setNotAfter(validityEndDate);
+ certGen.setPublicKey(keyPair.getPublic());
+ certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
+
+ final X509Certificate cert = certGen.generate(keyPair.getPrivate(), "AndroidOpenSSL");
+ return new CertificateAndKey(cert, keyPair.getPrivate());
+ }
+}
diff --git a/services/tests/VpnTests/java/android/net/VpnManagerTest.java b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
new file mode 100644
index 0000000..f5b83f0
--- /dev/null
+++ b/services/tests/VpnTests/java/android/net/VpnManagerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.mock.MockContext;
+import android.util.SparseArray;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.MessageUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link VpnManager}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnManagerTest {
+
+ private static final String PKG_NAME = "fooPackage";
+
+ private static final String SERVER_ADDR_STRING = "1.2.3.4";
+ private static final String IDENTITY_STRING = "Identity";
+ private static final byte[] PSK_BYTES = "preSharedKey".getBytes();
+
+ private IVpnManager mMockService;
+ private VpnManager mVpnManager;
+ private final MockContext mMockContext =
+ new MockContext() {
+ @Override
+ public String getOpPackageName() {
+ return PKG_NAME;
+ }
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ assumeFalse("Skipping test because watches don't support VPN",
+ InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_WATCH));
+ mMockService = mock(IVpnManager.class);
+ mVpnManager = new VpnManager(mMockContext, mMockService);
+ }
+
+ @Test
+ public void testProvisionVpnProfilePreconsented() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(true);
+
+ // Expect there to be no intent returned, as consent has already been granted.
+ assertNull(mVpnManager.provisionVpnProfile(profile));
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testProvisionVpnProfileNeedsConsent() throws Exception {
+ final PlatformVpnProfile profile = getPlatformVpnProfile();
+ when(mMockService.provisionVpnProfile(any(VpnProfile.class), eq(PKG_NAME)))
+ .thenReturn(false);
+
+ // Expect intent to be returned, as consent has not already been granted.
+ final Intent intent = mVpnManager.provisionVpnProfile(profile);
+ assertNotNull(intent);
+
+ final ComponentName expectedComponentName =
+ ComponentName.unflattenFromString(
+ "com.android.vpndialogs/com.android.vpndialogs.PlatformVpnConfirmDialog");
+ assertEquals(expectedComponentName, intent.getComponent());
+ verify(mMockService).provisionVpnProfile(eq(profile.toVpnProfile()), eq(PKG_NAME));
+ }
+
+ @Test
+ public void testDeleteProvisionedVpnProfile() throws Exception {
+ mVpnManager.deleteProvisionedVpnProfile();
+ verify(mMockService).deleteVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStartProvisionedVpnProfile() throws Exception {
+ mVpnManager.startProvisionedVpnProfile();
+ verify(mMockService).startVpnProfile(eq(PKG_NAME));
+ }
+
+ @Test
+ public void testStopProvisionedVpnProfile() throws Exception {
+ mVpnManager.stopProvisionedVpnProfile();
+ verify(mMockService).stopVpnProfile(eq(PKG_NAME));
+ }
+
+ private Ikev2VpnProfile getPlatformVpnProfile() throws Exception {
+ return new Ikev2VpnProfile.Builder(SERVER_ADDR_STRING, IDENTITY_STRING)
+ .setBypassable(true)
+ .setMaxMtu(1300)
+ .setMetered(true)
+ .setAuthPsk(PSK_BYTES)
+ .build();
+ }
+
+ @Test
+ public void testVpnTypesEqual() throws Exception {
+ SparseArray<String> vmVpnTypes = MessageUtils.findMessageNames(
+ new Class[] { VpnManager.class }, new String[]{ "TYPE_VPN_" });
+ SparseArray<String> nativeVpnType = MessageUtils.findMessageNames(
+ new Class[] { NativeVpnType.class }, new String[]{ "" });
+
+ // TYPE_VPN_NONE = -1 is only defined in VpnManager.
+ assertEquals(vmVpnTypes.size() - 1, nativeVpnType.size());
+ for (int i = VpnManager.TYPE_VPN_SERVICE; i < vmVpnTypes.size(); i++) {
+ assertEquals(vmVpnTypes.get(i), "TYPE_VPN_" + nativeVpnType.get(i));
+ }
+ }
+}
diff --git a/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
new file mode 100644
index 0000000..acbe8b8
--- /dev/null
+++ b/services/tests/VpnTests/java/com/android/internal/net/VpnProfileTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.net;
+
+import static android.net.cts.util.IkeSessionTestUtils.CHILD_PARAMS;
+import static android.net.cts.util.IkeSessionTestUtils.IKE_PARAMS_V4;
+
+import static com.android.testutils.ParcelUtils.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.IpSecAlgorithm;
+import android.net.ipsec.ike.IkeTunnelConnectionParams;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link VpnProfile}. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VpnProfileTest {
+ private static final String DUMMY_PROFILE_KEY = "Test";
+
+ private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+ private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+ private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
+ private static final int ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION = 26;
+ private static final int ENCODED_INDEX_IKE_TUN_CONN_PARAMS = 27;
+ private static final int ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED = 28;
+ private static final int ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED = 29;
+
+ @Test
+ public void testDefaults() throws Exception {
+ final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
+
+ assertEquals(DUMMY_PROFILE_KEY, p.key);
+ assertEquals("", p.name);
+ assertEquals(VpnProfile.TYPE_PPTP, p.type);
+ assertEquals("", p.server);
+ assertEquals("", p.username);
+ assertEquals("", p.password);
+ assertEquals("", p.dnsServers);
+ assertEquals("", p.searchDomains);
+ assertEquals("", p.routes);
+ assertTrue(p.mppe);
+ assertEquals("", p.l2tpSecret);
+ assertEquals("", p.ipsecIdentifier);
+ assertEquals("", p.ipsecSecret);
+ assertEquals("", p.ipsecUserCert);
+ assertEquals("", p.ipsecCaCert);
+ assertEquals("", p.ipsecServerCert);
+ assertEquals(null, p.proxy);
+ assertTrue(p.getAllowedAlgorithms() != null && p.getAllowedAlgorithms().isEmpty());
+ assertFalse(p.isBypassable);
+ assertFalse(p.isMetered);
+ assertEquals(1360, p.maxMtu);
+ assertFalse(p.areAuthParamsInline);
+ assertFalse(p.isRestrictedToTestNetworks);
+ assertFalse(p.excludeLocalRoutes);
+ assertFalse(p.requiresInternetValidation);
+ assertFalse(p.automaticNattKeepaliveTimerEnabled);
+ assertFalse(p.automaticIpVersionSelectionEnabled);
+ }
+
+ private VpnProfile getSampleIkev2Profile(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ null /* ikeTunConnParams */, true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
+ p.server = "bar";
+ p.username = "baz";
+ p.password = "qux";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.l2tpSecret = "";
+ p.ipsecIdentifier = "quux";
+ p.ipsecSecret = "quuz";
+ p.ipsecUserCert = "corge";
+ p.ipsecCaCert = "grault";
+ p.ipsecServerCert = "garply";
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ private VpnProfile getSampleIkev2ProfileWithIkeTunConnParams(String key) {
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */,
+ false /* excludesLocalRoutes */, true /* requiresPlatformValidation */,
+ new IkeTunnelConnectionParams(IKE_PARAMS_V4, CHILD_PARAMS),
+ true /* mAutomaticNattKeepaliveTimerEnabled */,
+ true /* automaticIpVersionSelectionEnabled */);
+
+ p.name = "foo";
+ p.server = "bar";
+ p.dnsServers = "8.8.8.8";
+ p.searchDomains = "";
+ p.routes = "0.0.0.0/0";
+ p.mppe = false;
+ p.proxy = null;
+ p.setAllowedAlgorithms(
+ Arrays.asList(
+ IpSecAlgorithm.AUTH_CRYPT_AES_GCM,
+ IpSecAlgorithm.AUTH_CRYPT_CHACHA20_POLY1305,
+ IpSecAlgorithm.AUTH_HMAC_SHA512,
+ IpSecAlgorithm.CRYPT_AES_CBC));
+ p.isBypassable = true;
+ p.isMetered = true;
+ p.maxMtu = 1350;
+ p.areAuthParamsInline = true;
+
+ // Not saved, but also not compared.
+ p.saveLogin = true;
+
+ return p;
+ }
+
+ @Test
+ public void testEquals() {
+ assertEquals(
+ getSampleIkev2Profile(DUMMY_PROFILE_KEY), getSampleIkev2Profile(DUMMY_PROFILE_KEY));
+
+ final VpnProfile modified = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ modified.maxMtu--;
+ assertNotEquals(getSampleIkev2Profile(DUMMY_PROFILE_KEY), modified);
+ }
+
+ @Test
+ public void testParcelUnparcel() {
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 28);
+ assertParcelSane(getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY), 28);
+ }
+
+ @Test
+ public void testEncodeDecodeWithIkeTunConnParams() {
+ final VpnProfile profile = getSampleIkev2ProfileWithIkeTunConnParams(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecode() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testEncodeDecodeTooManyValues() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final byte[] tooManyValues =
+ (new String(profile.encode()) + VpnProfile.VALUE_DELIMITER + "invalid").getBytes();
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
+ }
+
+ private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+ // Sort to ensure when we remove, we can do it from greatest first.
+ Arrays.sort(missingIndices);
+
+ final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+ final List<String> parts =
+ new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+ // Remove from back first to ensure indexing is consistent.
+ for (int i = missingIndices.length - 1; i >= 0; i--) {
+ parts.remove(missingIndices[i]);
+ }
+
+ return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+ }
+
+ @Test
+ public void testEncodeDecodeInvalidNumberOfValues() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_AUTH_PARAMS_INLINE,
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED
+ /* missingIndices */);
+
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+ }
+
+ private String getEncodedDecodedIkev2ProfileWithtooFewValues() {
+ return getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+ ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE,
+ ENCODED_INDEX_REQUIRE_PLATFORM_VALIDATION,
+ ENCODED_INDEX_IKE_TUN_CONN_PARAMS,
+ ENCODED_INDEX_AUTOMATIC_NATT_KEEPALIVE_TIMER_ENABLED,
+ ENCODED_INDEX_AUTOMATIC_IP_VERSION_SELECTION_ENABLED /* missingIndices */);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.isRestrictedToTestNetworks);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingExcludeLocalRoutes() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without excludeLocalRoutes defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.excludeLocalRoutes);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingRequiresValidation() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without requiresValidation defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.requiresInternetValidation);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticNattKeepaliveTimerEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticNattKeepaliveTimerEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticNattKeepaliveTimerEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeMissingAutomaticIpVersionSelectionEnabled() {
+ final String tooFewValues = getEncodedDecodedIkev2ProfileWithtooFewValues();
+
+ // Verify decoding without automaticIpVersionSelectionEnabled defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.automaticIpVersionSelectionEnabled);
+ }
+
+ @Test
+ public void testEncodeDecodeLoginsNotSaved() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ profile.saveLogin = false;
+
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, profile.encode());
+ assertNotEquals(profile, decoded);
+
+ // Add the username/password back, everything else must be equal.
+ decoded.username = profile.username;
+ decoded.password = profile.password;
+ assertEquals(profile, decoded);
+ }
+
+ @Test
+ public void testClone() {
+ final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
+ final VpnProfile clone = profile.clone();
+ assertEquals(profile, clone);
+ assertNotSame(profile, clone);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java b/services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/net/LockdownVpnTrackerTest.java
rename to services/tests/VpnTests/java/com/android/server/net/LockdownVpnTrackerTest.java
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 9c9aeea..705dac5 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -19,6 +19,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
import static org.junit.Assert.assertArrayEquals;
@@ -72,7 +73,7 @@
private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG = 4000;
private static final int BRIGHTENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 1000;
private static final int DARKENING_LIGHT_DEBOUNCE_CONFIG_IDLE = 2000;
- private static final float DOZE_SCALE_FACTOR = 0.0f;
+ private static final float DOZE_SCALE_FACTOR = 0.54f;
private static final boolean RESET_AMBIENT_LUX_AFTER_WARMUP_CONFIG = false;
private static final int LIGHT_SENSOR_WARMUP_TIME = 0;
private static final int AMBIENT_LIGHT_HORIZON_SHORT = 1000;
@@ -87,6 +88,7 @@
@Mock SensorManager mSensorManager;
@Mock BrightnessMappingStrategy mBrightnessMappingStrategy;
@Mock BrightnessMappingStrategy mIdleBrightnessMappingStrategy;
+ @Mock BrightnessMappingStrategy mDozeBrightnessMappingStrategy;
@Mock HysteresisLevels mAmbientBrightnessThresholds;
@Mock HysteresisLevels mScreenBrightnessThresholds;
@Mock HysteresisLevels mAmbientBrightnessThresholdsIdle;
@@ -124,12 +126,15 @@
when(mBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DEFAULT);
when(mIdleBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+ when(mDozeBrightnessMappingStrategy.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_DOZE);
SparseArray<BrightnessMappingStrategy> brightnessMappingStrategyMap = new SparseArray<>();
brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
mBrightnessMappingStrategy);
brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_IDLE,
mIdleBrightnessMappingStrategy);
+ brightnessMappingStrategyMap.append(AUTO_BRIGHTNESS_MODE_DOZE,
+ mDozeBrightnessMappingStrategy);
mController = new AutomaticBrightnessController(
new AutomaticBrightnessController.Injector() {
@Override
@@ -1032,10 +1037,9 @@
float normalizedBrightness = 0.3f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
- when(mBrightnessMappingStrategy.getBrightness(eq(lux), eq(null), anyInt()))
- .thenReturn(normalizedBrightness);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
- when(mBrightnessThrottler.isThrottled()).thenReturn(true);
// Send a new sensor value, disable the sensor and verify
listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
@@ -1047,4 +1051,79 @@
mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
/* brightnessEvent= */ null), EPSILON);
}
+
+ @Test
+ public void testAutoBrightnessInDoze_ShouldScaleIfNotUsingDozeCurve() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Set policy to DOZE
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ true);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // The brightness should be scaled by the doze factor
+ assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+ mController.getAutomaticScreenBrightness(
+ /* brightnessEvent= */ null), EPSILON);
+ assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR,
+ mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ /* brightnessEvent= */ null), EPSILON);
+ }
+
+ @Test
+ public void testAutoBrightnessInDoze_ShouldNotScaleIfUsingDozeCurve() throws Exception {
+ ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor.forClass(SensorEventListener.class);
+ verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor),
+ eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
+ SensorEventListener listener = listenerCaptor.getValue();
+
+ // Set up system to return 0.3f as a brightness value
+ float lux = 100.0f;
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness = 0.3f;
+ when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux)).thenReturn(lux);
+ when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux)).thenReturn(lux);
+ when(mDozeBrightnessMappingStrategy.getBrightness(eq(lux), /* packageName= */ eq(null),
+ /* category= */ anyInt())).thenReturn(normalizedBrightness);
+ when(mBrightnessThrottler.getBrightnessCap()).thenReturn(BRIGHTNESS_MAX_FLOAT);
+
+ // Switch mode to DOZE
+ mController.switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+ // Set policy to DOZE
+ mController.configure(AUTO_BRIGHTNESS_ENABLED, /* configuration= */ null,
+ /* brightness= */ 0, /* userChangedBrightness= */ false, /* adjustment= */ 0,
+ /* userChanged= */ false, DisplayPowerRequest.POLICY_DOZE,
+ /* shouldResetShortTermModel= */ true);
+
+ // Send a new sensor value
+ listener.onSensorChanged(TestUtils.createSensorEvent(mLightSensor, (int) lux));
+
+ // The brightness should not be scaled by the doze factor
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightness(
+ /* brightnessEvent= */ null), EPSILON);
+ assertEquals(normalizedBrightness,
+ mController.getAutomaticScreenBrightnessBasedOnLastObservedLux(
+ /* brightnessEvent= */ null), EPSILON);
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
index fbb14c3..4409051 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayOffloadSessionImplTest.java
@@ -26,7 +26,6 @@
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManagerInternal;
-import android.os.PowerManager;
import org.junit.Before;
import org.junit.Test;
@@ -66,8 +65,6 @@
mSession.stopOffload();
assertFalse(mSession.isActive());
- verify(mDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
// An inactive session shouldn't be stopped again
mSession.stopOffload();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index e9315c8..76b7780 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -22,6 +22,7 @@
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -77,6 +78,7 @@
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.brightness.BrightnessReason;
import com.android.server.display.brightness.clamper.BrightnessClamperController;
import com.android.server.display.brightness.clamper.HdrClamper;
import com.android.server.display.color.ColorDisplayService;
@@ -112,6 +114,8 @@
private static final int SECOND_FOLLOWER_DISPLAY_ID = FOLLOWER_DISPLAY_ID + 1;
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
+ private static final float DOZE_SCALE_FACTOR = 0.34f;
+
private static final float BRIGHTNESS_RAMP_RATE_MINIMUM = 0.0f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_DECREASE = 0.3f;
private static final float BRIGHTNESS_RAMP_RATE_FAST_INCREASE = 0.4f;
@@ -191,6 +195,9 @@
mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_displayColorFadeDisabled, false);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
+ DOZE_SCALE_FACTOR);
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
@@ -1559,6 +1566,43 @@
verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+ }
+
+ @Test
+ public void testBrightness_AutomaticHigherPriorityThanOffload() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+ float brightness = 0.34f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(PowerManager.BRIGHTNESS_INVALID_FLOAT);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+
+ mHolder.dpc.setBrightnessFromOffload(brightness);
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_OFFLOAD, mHolder.dpc.mBrightnessReason.getReason());
+
+ // Now automatic brightness becomes available
+ brightness = 0.22f;
+ when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
+ any(BrightnessEvent.class))).thenReturn(brightness);
+
+ mHolder.dpc.updateBrightness();
+ advanceTime(1); // Run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness), anyFloat(),
+ eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
+ assertEquals(BrightnessReason.REASON_AUTOMATIC, mHolder.dpc.mBrightnessReason.getReason());
}
@Test
@@ -1666,7 +1710,7 @@
}
@Test
- public void testInitialDozeBrightness() {
+ public void testInitialDozeBrightness_AutoBrightnessEnabled() {
when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
@@ -1695,6 +1739,39 @@
}
@Test
+ public void testInitialDozeBrightness_AutoBrightnessDisabled() {
+ when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
+ mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+ float brightness = 0.277f;
+ when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
+ when(mHolder.brightnessSetting.getBrightness()).thenReturn(brightness);
+ when(mHolder.hbmController.getCurrentBrightnessMax())
+ .thenReturn(PowerManager.BRIGHTNESS_MAX);
+ when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+ DisplayPowerRequest dpr = new DisplayPowerRequest();
+ dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+ mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+ advanceTime(1); // Run updatePowerState, initialize
+
+ ArgumentCaptor<BrightnessSetting.BrightnessSettingListener> listenerCaptor =
+ ArgumentCaptor.forClass(BrightnessSetting.BrightnessSettingListener.class);
+ verify(mHolder.brightnessSetting).registerListener(listenerCaptor.capture());
+ BrightnessSetting.BrightnessSettingListener listener = listenerCaptor.getValue();
+ listener.onBrightnessChanged(brightness);
+ advanceTime(1); // Send messages, run updatePowerState
+
+ verify(mHolder.animator).animateTo(eq(brightness * DOZE_SCALE_FACTOR),
+ /* linearSecondTarget= */ anyFloat(), /* rate= */ anyFloat(),
+ /* ignoreAnimationLimits= */ anyBoolean());
+ assertEquals(brightness * DOZE_SCALE_FACTOR, mHolder.dpc.getDozeBrightnessForOffload(),
+ /* delta= */ 0);
+ }
+
+ @Test
public void testInitialDozeBrightness_AbcIsNull() {
when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
when(mDisplayManagerFlagsMock.isDisplayOffloadEnabled()).thenReturn(true);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b99ecf3..14de527 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -46,7 +46,6 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
-import android.os.PowerManager;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.SurfaceControl;
@@ -1229,8 +1228,6 @@
verify(mDisplayOffloader).stopOffload();
assertFalse(mDisplayOffloadSession.isActive());
- verify(mMockedDisplayPowerController).setBrightnessFromOffload(
- PowerManager.BRIGHTNESS_INVALID_FLOAT);
}
private void initDisplayOffloadSession() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index 289d54b..9b6cc0a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -393,7 +393,31 @@
OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
offloadBrightnessStrategy);
- mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness);
+ assertTrue(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_OffloadStrategyNull() {
+ float brightness = 0.4f;
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ assertFalse(brightnessUpdated);
+ }
+
+ @Test
+ public void setBrightnessFromOffload_BrightnessUnchanged() {
+ float brightness = 0.4f;
+ OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class);
+ when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness);
+ when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(
+ offloadBrightnessStrategy);
+ boolean brightnessUpdated =
+ mDisplayBrightnessController.setBrightnessFromOffload(brightness);
+ verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness);
+ assertFalse(brightnessUpdated);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
index 1c681ce..a3728c8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java
@@ -18,8 +18,10 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.eq;
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.ContentResolver;
@@ -247,6 +249,7 @@
displayPowerRequest.screenBrightnessOverride = Float.NaN;
when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN);
when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN);
+ when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true);
when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f);
assertEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy(
displayPowerRequest, Display.STATE_ON));
@@ -267,4 +270,33 @@
mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest,
Display.STATE_ON));
}
+
+ @Test
+ public void selectStrategyCallsPostProcessorForAllStrategies() {
+ when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true);
+ mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext,
+ mInjector, DISPLAY_ID, mDisplayManagerFlags);
+ DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
+ DisplayManagerInternal.DisplayPowerRequest.class);
+ when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f);
+
+ mDisplayBrightnessStrategySelector.selectStrategy(displayPowerRequest, Display.STATE_ON);
+
+ StrategySelectionNotifyRequest strategySelectionNotifyRequest =
+ new StrategySelectionNotifyRequest(mFollowerBrightnessStrategy);
+ verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mBoostBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor(
+ eq(strategySelectionNotifyRequest));
+ }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index ba462e3..a5dc668 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -188,29 +188,6 @@
}
@Test
- public void testAutoBrightnessState_BrightnessReasonIsOffload() {
- mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
- int targetDisplayState = Display.STATE_ON;
- boolean allowAutoBrightnessWhileDozing = false;
- int brightnessReason = BrightnessReason.REASON_OFFLOAD;
- int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
- float lastUserSetBrightness = 0.2f;
- boolean userSetBrightnessChanged = true;
- mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments();
- mAutomaticBrightnessStrategy.setAutoBrightnessState(targetDisplayState,
- allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness,
- userSetBrightnessChanged);
- verify(mAutomaticBrightnessController)
- .configure(AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED,
- mBrightnessConfiguration,
- lastUserSetBrightness,
- userSetBrightnessChanged, 0.5f,
- false, policy, true);
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessEnabled());
- assertFalse(mAutomaticBrightnessStrategy.isAutoBrightnessDisabledDueToDisplayOff());
- }
-
- @Test
public void testAutoBrightnessState_DisplayIsInDoze_ConfigDoesAllow() {
mAutomaticBrightnessStrategy.setUseAutoBrightness(true);
int targetDisplayState = Display.STATE_DOZE;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
index fb47aa8..9d3caa5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -98,8 +98,6 @@
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[1];
-
@Mock
AppOpsService mAppOpsService;
@Mock
@@ -120,6 +118,7 @@
HandlerThread mHandlerThread;
TestLooperManager mLooper;
AtomicInteger mNextPid;
+ BroadcastHistory mEmptyHistory;
/**
* Map from PID to registered registered runtime receivers.
@@ -137,6 +136,13 @@
.acquireLooperManager(mHandlerThread.getLooper()));
mNextPid = new AtomicInteger(100);
+ mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ mEmptyHistory = new BroadcastHistory(mConstants) {
+ public void addBroadcastToHistoryLocked(BroadcastRecord original) {
+ // Ignored
+ }
+ };
+
LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
@@ -164,8 +170,6 @@
mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
- mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
}
public void tearDown() throws Exception {
@@ -213,8 +217,8 @@
}
@Override
- public BroadcastQueue[] getBroadcastQueues(ActivityManagerService service) {
- return mBroadcastQueues;
+ public BroadcastQueue getBroadcastQueue(ActivityManagerService service) {
+ return null;
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index cc6fc80..bcf297f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -118,15 +118,9 @@
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
mImpl = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- mBroadcastQueues[0] = mImpl;
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mImpl);
doReturn(1L).when(mQueue1).getRunnableAt();
doReturn(2L).when(mQueue2).getRunnableAt();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 3f6117b..56e5bd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -85,12 +85,8 @@
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.After;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.verification.VerificationMode;
@@ -100,7 +96,6 @@
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -114,18 +109,10 @@
* Common tests for {@link BroadcastQueue} implementations.
*/
@MediumTest
-@RunWith(Parameterized.class)
@SuppressWarnings("GuardedBy")
public class BroadcastQueueTest extends BaseBroadcastQueueTest {
private static final String TAG = "BroadcastQueueTest";
- private final Impl mImpl;
-
- private enum Impl {
- DEFAULT,
- MODERN,
- }
-
private BroadcastQueue mQueue;
private UidObserver mUidObserver;
@@ -157,15 +144,6 @@
*/
private List<Pair<Integer, String>> mScheduledBroadcasts = new ArrayList<>();
- @Parameters(name = "impl={0}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { {Impl.DEFAULT}, {Impl.MODERN} });
- }
-
- public BroadcastQueueTest(Impl impl) {
- mImpl = impl;
- }
-
@Before
public void setUp() throws Exception {
super.setUp();
@@ -251,24 +229,9 @@
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
- public void addBroadcastToHistoryLocked(BroadcastRecord original) {
- // Ignored
- }
- };
-
- if (mImpl == Impl.DEFAULT) {
- mQueue = new BroadcastQueueImpl(mAms, mHandlerThread.getThreadHandler(), TAG,
- mConstants, mSkipPolicy, emptyHistory, false,
- ProcessList.SCHED_GROUP_DEFAULT);
- } else if (mImpl == Impl.MODERN) {
- mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
- mConstants, mConstants, mSkipPolicy, emptyHistory);
- } else {
- throw new UnsupportedOperationException();
- }
- mBroadcastQueues[0] = mQueue;
-
+ mQueue = new BroadcastQueueModernImpl(mAms, mHandlerThread.getThreadHandler(),
+ mConstants, mConstants, mSkipPolicy, mEmptyHistory);
+ mAms.setBroadcastQueueForTest(mQueue);
mQueue.start(mContext.getContentResolver());
// Set the constants after invoking BroadcastQueue.start() to ensure they don't
@@ -489,10 +452,8 @@
}
private void assertHealth() {
- if (mImpl == Impl.MODERN) {
- // If this fails, it'll throw a clear reason message
- ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
- }
+ // If this fails, it'll throw a clear reason message
+ ((BroadcastQueueModernImpl) mQueue).assertHealthLocked();
}
private static Map<String, Object> asMap(Bundle bundle) {
@@ -814,18 +775,13 @@
.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
verify(mAms, times(2)).enqueueOomAdjTargetLocked(eq(receiverApp));
- if ((mImpl == Impl.DEFAULT) && (receiverApp == receiverBlueApp)) {
- // Nuance: the default implementation doesn't ask for manifest
- // cold-started apps to be thawed, but the modern stack does
- } else {
- // Confirm that app was thawed
- verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
+ // Confirm that app was thawed
+ verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
- // Confirm that we added package to process
- verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
- anyLong(), any());
- }
+ // Confirm that we added package to process
+ verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
+ anyLong(), any());
// Confirm that we've reported package as being used
verify(mAms, atLeastOnce()).notifyPackageUse(eq(receiverApp.info.packageName),
@@ -868,9 +824,6 @@
*/
@Test
public void testWedged_Registered_Ordered() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -891,9 +844,6 @@
*/
@Test
public void testWedged_Registered_ResultTo() throws Exception {
- // Legacy stack doesn't detect these ANRs; likely an oversight
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
ProcessBehavior.WEDGE);
@@ -994,9 +944,7 @@
getUidForPackage(PACKAGE_GREEN));
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
final ProcessRecord receiverBlueApp = mAms.getProcessRecordLocked(PACKAGE_BLUE,
getUidForPackage(PACKAGE_BLUE));
verifyScheduleReceiver(receiverBlueApp, airplane);
@@ -1110,10 +1058,8 @@
waitForIdle();
// Legacy stack does not remove registered receivers as part of
// cleanUpDisabledPackageReceiversLocked() call, so verify this only on modern queue.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), callerApp, USER_GUEST);
- verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
- }
+ verifyScheduleReceiver(never(), callerApp, USER_GUEST);
+ verifyScheduleRegisteredReceiver(never(), callerApp, USER_GUEST);
for (String pkg : new String[] {
PACKAGE_GREEN, PACKAGE_BLUE, PACKAGE_YELLOW
}) {
@@ -1199,9 +1145,6 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_AVOID_REPEATED_BCAST_RE_ENQUEUES)
public void testRepeatedKillWithoutNotify() throws Exception {
- // Legacy queue does not handle repeated kills that don't get notified.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1227,9 +1170,7 @@
// Modern queue always kills the target process when broadcast delivery fails, where as
// the legacy queue leaves the process killing task to AMS
- if (mImpl == Impl.MODERN) {
- assertNull(receiverGreenApp);
- }
+ assertNull(receiverGreenApp);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
verifyScheduleReceiver(times(1), receiverYellowApp, airplane);
verifyScheduleReceiver(times(1), receiverOrangeApp, timezone);
@@ -1273,11 +1214,7 @@
assertNotEquals(receiverBlueApp, restartedReceiverBlueApp);
// Legacy queue will always try delivering the broadcast even if the process
// has been killed.
- if (mImpl == Impl.MODERN) {
- verifyScheduleReceiver(never(), receiverBlueApp, airplane);
- } else {
- verifyScheduleReceiver(times(1), receiverBlueApp, airplane);
- }
+ verifyScheduleReceiver(never(), receiverBlueApp, airplane);
// Verify that the new process receives the broadcast.
verifyScheduleReceiver(times(1), restartedReceiverBlueApp, airplane);
}
@@ -1671,9 +1608,6 @@
*/
@Test
public void testPrioritized_withDeferrableBroadcasts() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -1834,10 +1768,6 @@
@Test
public void testReplacePending_withUrgentBroadcast() throws Exception {
- // The behavior is same with the legacy queue but AMS takes care of finding
- // the right queue and replacing the broadcast.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final Intent timeTickFirst = new Intent(Intent.ACTION_TIME_TICK);
@@ -1903,15 +1833,9 @@
waitForIdle();
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
- verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(2), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverYellowApp, airplane);
}
@Test
@@ -1931,14 +1855,10 @@
withPriority(receiverGreenA, 5))));
waitForIdle();
- if (mImpl == Impl.DEFAULT) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- // In the modern queue, we don't end up replacing the old broadcast to
- // avoid creating priority inversion and so the process will receive
- // both the old and new broadcasts.
- verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
- }
+ // In the modern queue, we don't end up replacing the old broadcast to
+ // avoid creating priority inversion and so the process will receive
+ // both the old and new broadcasts.
+ verifyScheduleRegisteredReceiver(times(3), receiverGreenApp, airplane);
}
@Test
@@ -1966,11 +1886,7 @@
verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, timeTick);
- if (mImpl == Impl.MODERN) {
- verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
- } else {
- verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
- }
+ verifyScheduleRegisteredReceiver(times(2), receiverGreenApp, airplane);
verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
}
@@ -2001,6 +1917,56 @@
}
@Test
+ public void testReplacePendingToCachedProcess_withDeferrableBroadcast() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+
+ setProcessFreezable(receiverGreenApp, true, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+ final BroadcastOptions opts = BroadcastOptions.makeBasic()
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
+
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp, 10);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp, 5);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp, 0);
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ // Enqueue the broadcast again to replace the earlier one
+ enqueueBroadcast(makeBroadcastRecord(timeTick, callerApp, opts, List.of(
+ receiverGreen, receiverBlue, receiverYellow)));
+
+ waitForIdle();
+ // Green should still be in the cached state and shouldn't receive the broadcast
+ verifyScheduleRegisteredReceiver(never(), receiverGreenApp, timeTick);
+
+ final IApplicationThread blueThread = receiverBlueApp.getThread();
+ final IApplicationThread yellowThread = receiverYellowApp.getThread();
+ final InOrder inOrder = inOrder(blueThread, yellowThread);
+ inOrder.verify(blueThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+ inOrder.verify(yellowThread).scheduleRegisteredReceiver(
+ any(), argThat(filterEqualsIgnoringComponent(timeTick)),
+ anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyBoolean(),
+ eq(UserHandle.USER_SYSTEM), anyInt(), anyInt(), any());
+
+ setProcessFreezable(receiverGreenApp, false, false);
+ mQueue.onProcessFreezableChangedLocked(receiverGreenApp);
+ waitForIdle();
+
+ // Confirm that green receives the broadcast once it comes out of the cached state
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, timeTick);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2171,16 +2137,9 @@
}
waitForIdle();
- final int expectedTimes;
- switch (mImpl) {
- // Original stack requested for every single receiver; yikes
- case DEFAULT: expectedTimes = 64; break;
- // Modern stack requests once each time we promote a process to
- // running; we promote "green" twice, and "blue" and "yellow" once
- case MODERN: expectedTimes = 4; break;
- default: throw new UnsupportedOperationException();
- }
-
+ // Modern stack requests once each time we promote a process to
+ // running; we promote "green" twice, and "blue" and "yellow" once
+ final int expectedTimes = 4;
verify(mAms, times(expectedTimes))
.updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
}
@@ -2249,9 +2208,6 @@
*/
@Test
public void testDeferralPolicy_UntilActive() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
@@ -2297,9 +2253,6 @@
*/
@Test
public void testDeferralPolicy_UntilActive_WithMultiProcessUid() throws Exception {
- // Legacy stack doesn't support deferral
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverGreenApp1 = makeActiveProcessRecord(PACKAGE_GREEN);
final ProcessRecord receiverGreenApp2 = makeActiveProcessRecord(PACKAGE_GREEN,
@@ -2331,9 +2284,6 @@
@Test
public void testBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
@@ -2365,9 +2315,6 @@
@Test
public void testPrioritizedBroadcastDelivery_uidForeground() throws Exception {
- // Legacy stack doesn't support prioritization to foreground app.
- Assume.assumeTrue(mImpl == Impl.MODERN);
-
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index f0efb79..878b945 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -61,8 +61,6 @@
import androidx.test.filters.SmallTest;
-import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -747,68 +745,6 @@
}
}
- /**
- * Test the class {@link BroadcastDispatcher#DeferredBootCompletedBroadcastPerUser}
- */
- @Test
- public void testDeferBootCompletedBroadcast_dispatcher() {
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, false);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_LOCKED_BOOT_COMPLETED, true);
- testDeferBootCompletedBroadcast_dispatcher_internal(ACTION_BOOT_COMPLETED, true);
- }
-
- private void testDeferBootCompletedBroadcast_dispatcher_internal(String action,
- boolean isAllUidReady) {
- final List<ResolveInfo> receivers = createReceiverInfos(PACKAGE_LIST, new int[] {USER0});
- final BroadcastRecord br = createBroadcastRecord(receivers, USER0, new Intent(action));
- assertEquals(PACKAGE_LIST.length, br.receivers.size());
-
- SparseArray<BroadcastRecord> deferred = br.splitDeferredBootCompletedBroadcastLocked(
- mActivityManagerInternal, DEFER_BOOT_COMPLETED_BROADCAST_ALL);
- // original BroadcastRecord receivers list is empty now.
- assertTrue(br.receivers.isEmpty());
- assertEquals(PACKAGE_LIST.length, deferred.size());
-
- DeferredBootCompletedBroadcastPerUser deferredPerUser =
- new DeferredBootCompletedBroadcastPerUser(USER0);
- deferredPerUser.enqueueBootCompletedBroadcasts(action, deferred);
-
- if (action.equals(ACTION_LOCKED_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredLockedBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mLockedBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForLockedBootCompletedBroadcast.size());
- } else if (action.equals(ACTION_BOOT_COMPLETED)) {
- assertEquals(PACKAGE_LIST.length,
- deferredPerUser.mDeferredBootCompletedBroadcasts.size());
- assertTrue(deferredPerUser.mBootCompletedBroadcastReceived);
- for (int i = 0; i < PACKAGE_LIST.length; i++) {
- final int uid = UserHandle.getUid(USER0, getAppId(i));
- if (!isAllUidReady) {
- deferredPerUser.updateUidReady(uid);
- }
- BroadcastRecord d = deferredPerUser.dequeueDeferredBootCompletedBroadcast(
- isAllUidReady);
- final ResolveInfo info = (ResolveInfo) d.receivers.get(0);
- assertEquals(PACKAGE_LIST[i], info.activityInfo.applicationInfo.packageName);
- assertEquals(uid, info.activityInfo.applicationInfo.uid);
- }
- assertEquals(0, deferredPerUser.mUidReadyForBootCompletedBroadcast.size());
- }
- }
-
private static void cleanupDisabledPackageReceivers(BroadcastRecord record,
String packageName, int userId) {
record.cleanupDisabledPackageReceiversLocked(packageName, null /* filterByClasses */,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 1226f0c..872ac40 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -297,6 +297,10 @@
} else {
updateProcessRecordNodes(Arrays.asList(apps));
if (apps.length == 1) {
+ final ProcessRecord app = apps[0];
+ if (!sService.mProcessList.getLruProcessesLOSP().contains(app)) {
+ sService.mProcessList.getLruProcessesLOSP().add(app);
+ }
sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
@@ -475,7 +479,16 @@
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
updateOomAdj(app);
- assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, CACHED_APP_MIN_ADJ,
+ final int expectedAdj;
+ if (sService.mConstants.ENABLE_NEW_OOMADJ) {
+ // A cached empty process can be at best a level higher than the min cached adj.
+ expectedAdj = sFirstCachedAdj;
+ } else {
+ // This is wrong but legacy behavior is going to be removed and not worth fixing.
+ expectedAdj = CACHED_APP_MIN_ADJ;
+ }
+
+ assertProcStates(app, PROCESS_STATE_CACHED_EMPTY, expectedAdj,
SCHED_GROUP_BACKGROUND);
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 6bcd778..c6a6865 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -60,10 +60,13 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
@@ -71,12 +74,15 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.net.NetworkRequest;
import android.os.Looper;
import android.os.PowerManager;
+import android.os.UserHandle;
import android.provider.DeviceConfig;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
import android.util.ArraySet;
import android.util.EmptyArray;
import android.util.SparseArray;
@@ -104,6 +110,9 @@
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.Executor;
public class FlexibilityControllerTest {
@@ -113,6 +122,9 @@
private MockitoSession mMockingSession;
private BroadcastReceiver mBroadcastReceiver;
+ private final SparseArray<ArraySet<String>> mCarrierPrivilegedApps = new SparseArray<>();
+ private final SparseArray<TelephonyManager.CarrierPrivilegesCallback>
+ mCarrierPrivilegedCallbacks = new SparseArray<>();
private FlexibilityController mFlexibilityController;
private DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
private JobStore mJobStore;
@@ -130,6 +142,10 @@
@Mock
private PrefetchController mPrefetchController;
@Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
private PackageManager mPackageManager;
@Before
@@ -138,6 +154,7 @@
.initMocks(this)
.strictness(Strictness.LENIENT)
.spyStatic(DeviceConfig.class)
+ .mockStatic(AppGlobals.class)
.mockStatic(LocalServices.class)
.startMocking();
// Called in StateController constructor.
@@ -167,17 +184,23 @@
-> mDeviceConfigPropertiesBuilder.build())
.when(() -> DeviceConfig.getProperties(
eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
+ // Used in FlexibilityController.SpecialAppTracker.
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION))
+ .thenReturn(true);
//used to get jobs by UID
mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
doReturn(mJobStore).when(mJobSchedulerService).getJobStore();
// Used in JobStatus.
- doReturn(mock(PackageManagerInternal.class))
- .when(() -> LocalServices.getService(PackageManagerInternal.class));
+ doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
// Freeze the clocks at a moment in time
JobSchedulerService.sSystemClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
JobSchedulerService.sElapsedRealtimeClock =
Clock.fixed(Instant.ofEpochMilli(FROZEN_TIME), ZoneOffset.UTC);
+ // Set empty set of privileged apps.
+ setSimSlotMappings(null);
+ setPowerWhitelistExceptIdle();
// Initialize real objects.
doReturn(Long.MAX_VALUE).when(mPrefetchController).getNextEstimatedLaunchTimeLocked(any());
ArgumentCaptor<BroadcastReceiver> receiverCaptor =
@@ -249,9 +272,13 @@
}
private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
+ return createJobStatus(testTag, job, SOURCE_PACKAGE);
+ }
+
+ private JobStatus createJobStatus(String testTag, JobInfo.Builder job, String sourcePackage) {
JobInfo jobInfo = job.build();
JobStatus js = JobStatus.createFromJobInfo(
- jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
+ jobInfo, 1000, sourcePackage, SOURCE_USER_ID, "FCTest", testTag);
js.enqueueTime = FROZEN_TIME;
js.setStandbyBucket(ACTIVE_INDEX);
if (js.hasFlexibilityConstraint()) {
@@ -1084,7 +1111,6 @@
@Test
public void testAllowlistedAppBypass() {
- setPowerWhitelistExceptIdle();
mFlexibilityController.onSystemServicesReady();
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
@@ -1118,6 +1144,148 @@
}
@Test
+ public void testCarrierPrivilegedAppBypass() throws Exception {
+ mFlexibilityController.onSystemServicesReady();
+
+ final String carrier1Pkg1 = "com.test.carrier.1.pkg.1";
+ final String carrier1Pkg2 = "com.test.carrier.1.pkg.2";
+ final String carrier2Pkg = "com.test.carrier.2.pkg";
+ final String nonCarrierPkg = "com.test.normal.pkg";
+
+ setPackageUid(carrier1Pkg1, 1);
+ setPackageUid(carrier1Pkg2, 11);
+ setPackageUid(carrier2Pkg, 2);
+ setPackageUid(nonCarrierPkg, 3);
+
+ // Set the second carrier's privileged list before SIM configuration is sent to test
+ // initialization.
+ setCarrierPrivilegedAppList(2, carrier2Pkg);
+
+ UiccSlotMapping sim1 = mock(UiccSlotMapping.class);
+ UiccSlotMapping sim2 = mock(UiccSlotMapping.class);
+ doReturn(1).when(sim1).getLogicalSlotIndex();
+ doReturn(2).when(sim2).getLogicalSlotIndex();
+ setSimSlotMappings(List.of(sim1, sim2));
+
+ JobStatus jsHighC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg1);
+ JobStatus jsDefaultC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg1);
+ JobStatus jsLowC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg1);
+ JobStatus jsMinC1P1 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg1);
+ JobStatus jsHighC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier1Pkg2);
+ JobStatus jsDefaultC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier1Pkg2);
+ JobStatus jsLowC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier1Pkg2);
+ JobStatus jsMinC1P2 = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier1Pkg2);
+ JobStatus jsHighC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), carrier2Pkg);
+ JobStatus jsDefaultC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), carrier2Pkg);
+ JobStatus jsLowC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), carrier2Pkg);
+ JobStatus jsMinC2P = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), carrier2Pkg);
+ JobStatus jsHighNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_HIGH), nonCarrierPkg);
+ JobStatus jsDefaultNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_DEFAULT), nonCarrierPkg);
+ JobStatus jsLowNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_LOW), nonCarrierPkg);
+ JobStatus jsMinNCP = createJobStatus("testCarrierPrivilegedAppBypass",
+ createJob(0).setPriority(JobInfo.PRIORITY_MIN), nonCarrierPkg);
+
+ setCarrierPrivilegedAppList(1);
+ synchronized (mFlexibilityController.mLock) {
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Only mark the first package of carrier 1 as privileged. Only that app's jobs should
+ // be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Add the second package of carrier 1. Both apps' jobs should be exempted.
+ setCarrierPrivilegedAppList(1, carrier1Pkg1, carrier1Pkg2);
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+
+ // Remove a SIM slot. The relevant app's should no longer have exempted jobs.
+ setSimSlotMappings(List.of(sim1));
+ synchronized (mFlexibilityController.mLock) {
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P1));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P1));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC1P2));
+ assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC1P2));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinC2P));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsHighNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsDefaultNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsLowNCP));
+ assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(jsMinNCP));
+ }
+ }
+
+ @Test
public void testForegroundAppBypass() {
JobStatus jsHigh = createJobStatus("testAllowlistedAppBypass",
createJob(0).setPriority(JobInfo.PRIORITY_HIGH));
@@ -1753,6 +1921,24 @@
}
}
+ private void setCarrierPrivilegedAppList(int logicalIndex, String... packages) {
+ final ArraySet<String> packageSet = packages == null
+ ? new ArraySet<>() : new ArraySet<>(packages);
+ mCarrierPrivilegedApps.put(logicalIndex, packageSet);
+
+ TelephonyManager.CarrierPrivilegesCallback callback =
+ mCarrierPrivilegedCallbacks.get(logicalIndex);
+ if (callback != null) {
+ callback.onCarrierPrivilegesChanged(packageSet, Collections.emptySet());
+ waitForQuietModuleThread();
+ }
+ }
+
+ private void setPackageUid(final String pkgName, final int uid) throws Exception {
+ doReturn(uid).when(mIPackageManager)
+ .getPackageUid(eq(pkgName), anyLong(), eq(UserHandle.getUserId(uid)));
+ }
+
private void setPowerWhitelistExceptIdle(String... packages) {
doReturn(packages == null ? EmptyArray.STRING : packages)
.when(mDeviceIdleInternal).getFullPowerWhitelistExceptIdle();
@@ -1763,6 +1949,47 @@
}
}
+ private void setSimSlotMappings(@Nullable Collection<UiccSlotMapping> simSlotMapping) {
+ clearInvocations(mTelephonyManager);
+ final Collection<UiccSlotMapping> returnedMapping = simSlotMapping == null
+ ? Collections.emptyList() : simSlotMapping;
+ doReturn(returnedMapping).when(mTelephonyManager).getSimSlotMapping();
+ if (mBroadcastReceiver != null) {
+ final Intent intent = new Intent(TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED);
+ mBroadcastReceiver.onReceive(mContext, intent);
+ waitForQuietModuleThread();
+ }
+ if (returnedMapping.size() > 0) {
+ ArgumentCaptor<TelephonyManager.CarrierPrivilegesCallback> callbackCaptor =
+ ArgumentCaptor.forClass(TelephonyManager.CarrierPrivilegesCallback.class);
+ ArgumentCaptor<Integer> logicalIndexCaptor = ArgumentCaptor.forClass(Integer.class);
+
+ final int minExpectedNewRegistrations = Math.max(0,
+ returnedMapping.size() - mCarrierPrivilegedCallbacks.size());
+ verify(mTelephonyManager, atLeast(minExpectedNewRegistrations))
+ .registerCarrierPrivilegesCallback(
+ logicalIndexCaptor.capture(), any(), callbackCaptor.capture());
+
+ final List<Integer> registeredIndices = logicalIndexCaptor.getAllValues();
+ final List<TelephonyManager.CarrierPrivilegesCallback> registeredCallbacks =
+ callbackCaptor.getAllValues();
+ for (int i = 0; i < registeredIndices.size(); ++i) {
+ final int logicalIndex = registeredIndices.get(i);
+ final TelephonyManager.CarrierPrivilegesCallback callback =
+ registeredCallbacks.get(i);
+
+ mCarrierPrivilegedCallbacks.put(logicalIndex, callback);
+
+ // The API contract promises a callback upon registration with the current list.
+ final ArraySet<String> cpApps = mCarrierPrivilegedApps.get(logicalIndex);
+ callback.onCarrierPrivilegesChanged(
+ cpApps == null ? Collections.emptySet() : cpApps,
+ Collections.emptySet());
+ }
+ waitForQuietModuleThread();
+ }
+ }
+
private void setUidBias(int uid, int bias) {
int prevBias = mJobSchedulerService.getUidBias(uid);
doReturn(bias).when(mJobSchedulerService).getUidBias(uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 7bbcd50..bc7c9a5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.pm;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.content.pm.PackageManager.FEATURE_EMBEDDED;
+import static android.content.pm.PackageManager.FEATURE_LEANBACK;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
@@ -41,6 +45,7 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.KeyguardManager;
import android.content.Context;
@@ -48,6 +53,7 @@
import android.content.pm.UserInfo;
import android.multiuser.Flags;
import android.os.PowerManager;
+import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -76,6 +82,7 @@
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -120,11 +127,14 @@
private static final String TAG_RESTRICTIONS = "restrictions";
+ private static final String PRIVATE_PROFILE_NAME = "TestPrivateProfile";
+
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
.spyStatic(UserManager.class)
.spyStatic(LocalServices.class)
.spyStatic(SystemProperties.class)
+ .spyStatic(ActivityManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(Settings.Secure.class)
.build();
@@ -163,6 +173,7 @@
@Before
@UiThreadTest // Needed to initialize main handler
public void setFixtures() {
+ MockitoAnnotations.initMocks(this);
mSpiedContext = spy(mRealContext);
// Called when WatchedUserStates is constructed
@@ -172,11 +183,12 @@
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
- when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ doReturn(mKeyguardManager).when(mSpiedContext).getSystemService(KeyguardManager.class);
when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
+ mockIsLowRamDevice(false);
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -570,9 +582,10 @@
@Test
public void testAutoLockPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
@@ -587,10 +600,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
@@ -606,10 +620,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
@@ -623,10 +638,11 @@
@Test
public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
@@ -641,13 +657,14 @@
@Test
public void testAutoLockAfterInactityForPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
UserManagerService mSpiedUms = spy(mUms);
mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
when(mPowerManager.isInteractive()).thenReturn(false);
UserInfo privateProfileUser =
- mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
@@ -662,6 +679,7 @@
@Test
public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
@@ -675,8 +693,9 @@
@Test
public void testSetOrUpdateAutoLockPreference() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
- mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
// Set the preference to auto lock on device lock
@@ -733,6 +752,77 @@
}
}
+ @Test
+ public void testCreatePrivateProfileOnHeadlessSystemUser_shouldAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ UserManagerService mSpiedUms = spy(mUms);
+ int mainUser = mSpiedUms.getMainUserId();
+ doReturn(true).when(mSpiedUms).isHeadlessSystemUserMode();
+ assertThat(mSpiedUms.canAddPrivateProfile(mainUser)).isTrue();
+ assertThat(mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(
+ PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null)).isNotNull();
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnSecondaryUser_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ UserInfo user = mUms.createUserWithThrow(generateLongString(), USER_TYPE_FULL_SECONDARY, 0);
+ assertThat(mUms.canAddPrivateProfile(user.id)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, user.id, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnAutoDevices_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_AUTOMOTIVE), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnTV_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_LEANBACK), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnEmbedded_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_EMBEDDED), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
+ @Test
+ public void testCreatePrivateProfileOnWatch_shouldNotAllowCreation() {
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_BLOCK_PRIVATE_SPACE_CREATION);
+ doReturn(true).when(mMockPms).hasSystemFeature(eq(FEATURE_WATCH), anyInt());
+ int mainUser = mUms.getMainUserId();
+ assertThat(mUms.canAddPrivateProfile(0)).isFalse();
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME,
+ USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null));
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -800,6 +890,10 @@
any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
}
+ private void mockIsLowRamDevice(boolean isLowRamDevice) {
+ doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
+ }
+
private void mockDeviceDemoMode(boolean enabled) {
doReturn(enabled ? 1 : 0).when(() -> Settings.Global.getInt(
any(), eq(android.provider.Settings.Global.DEVICE_DEMO_MODE), anyInt()));
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
new file mode 100644
index 0000000..e94b8ad
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/Android.bp
@@ -0,0 +1,56 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "RollbackPackageHealthObserverTests",
+
+ srcs: [
+ "*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.runner",
+ "mockito-target-extended-minus-junit4",
+ "services.core",
+ "truth",
+ "flag-junit",
+ ],
+
+ libs: [
+ "android.test.mock",
+ "android.test.base",
+ "android.test.runner",
+ ],
+
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
+
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: [
+ "device-tests",
+ "automotive-tests",
+ ],
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
new file mode 100644
index 0000000..c52dbde
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.rollback">
+
+ <uses-sdk android:targetSdkVersion="35" />
+
+ <application android:testOnly="true"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.rollback"
+ android:label="Frameworks Rollback Package Health Observer test" />
+</manifest>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
new file mode 100644
index 0000000..635183c
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs Rollback Package Health Observer Tests.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="RollbackPackageHealthObserverTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="RollbackPackageHealthObserverTests" />
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.server.rollback" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
index e42bdad..4ac4484 100644
--- a/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/TEST_MAPPING
@@ -1,7 +1,7 @@
{
- "postsubmit": [
+ "presubmit": [
{
- "name": "FrameworksMockingServicesTests",
+ "name": "RollbackPackageHealthObserverTests",
"options": [
{
"include-filter": "com.android.server.rollback"
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
new file mode 100644
index 0000000..7ecc7fd
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
+import static android.app.WallpaperManager.PORTRAIT;
+import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.getOrientation;
+import static android.app.WallpaperManager.getRotatedOrientation;
+
+import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.util.SparseArray;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * Unit tests for the most important helpers of {@link WallpaperCropper}, in particular
+ * {@link WallpaperCropper#getCrop(Point, Point, SparseArray, boolean)}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@RequiresFlagsEnabled(FLAG_MULTI_CROP)
+public class WallpaperCropperTest {
+
+ @Mock
+ private WallpaperDisplayHelper mWallpaperDisplayHelper;
+ private WallpaperCropper mWallpaperCropper;
+
+ private static final Point PORTRAIT_ONE = new Point(500, 800);
+ private static final Point PORTRAIT_TWO = new Point(400, 1000);
+ private static final Point PORTRAIT_THREE = new Point(2000, 800);
+ private static final Point PORTRAIT_FOUR = new Point(1600, 1000);
+
+ private static final Point SQUARE_PORTRAIT_ONE = new Point(1000, 800);
+ private static final Point SQUARE_LANDSCAPE_ONE = new Point(800, 1000);
+
+ /**
+ * Common device: a single screen of portrait/landscape orientation
+ */
+ private static final List<Point> STANDARD_DISPLAY = List.of(PORTRAIT_ONE);
+
+ /** 1: folded: portrait, unfolded: square with w < h */
+ private static final List<Point> FOLDABLE_ONE = List.of(PORTRAIT_ONE, SQUARE_PORTRAIT_ONE);
+
+ /** 2: folded: portrait, unfolded: square with w > h */
+ private static final List<Point> FOLDABLE_TWO = List.of(PORTRAIT_TWO, SQUARE_LANDSCAPE_ONE);
+
+ /** 3: folded: square with w < h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_THREE = List.of(SQUARE_PORTRAIT_ONE, PORTRAIT_THREE);
+
+ /** 4: folded: square with w > h, unfolded: portrait */
+ private static final List<Point> FOLDABLE_FOUR = List.of(SQUARE_LANDSCAPE_ONE, PORTRAIT_FOUR);
+
+ /**
+ * List of different sets of displays for foldable devices. Foldable devices have two displays:
+ * a folded (smaller) unfolded (larger).
+ */
+ private static final List<List<Point>> ALL_FOLDABLE_DISPLAYS = List.of(
+ FOLDABLE_ONE, FOLDABLE_TWO, FOLDABLE_THREE, FOLDABLE_FOUR);
+
+ private SparseArray<Point> mDisplaySizes = new SparseArray<>();
+ private int mFolded = ORIENTATION_UNKNOWN;
+ private int mFoldedRotated = ORIENTATION_UNKNOWN;
+ private int mUnfolded = ORIENTATION_UNKNOWN;
+ private int mUnfoldedRotated = ORIENTATION_UNKNOWN;
+
+ private static final List<Integer> ALL_MODES = List.of(
+ WallpaperCropper.ADD, WallpaperCropper.REMOVE, WallpaperCropper.BALANCE);
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ mWallpaperCropper = new WallpaperCropper(mWallpaperDisplayHelper);
+ }
+
+ private void setUpWithDisplays(List<Point> displaySizes) {
+ mDisplaySizes = new SparseArray<>();
+ displaySizes.forEach(size -> {
+ mDisplaySizes.put(getOrientation(size), size);
+ Point rotated = new Point(size.y, size.x);
+ mDisplaySizes.put(getOrientation(rotated), rotated);
+ });
+ when(mWallpaperDisplayHelper.getDefaultDisplaySizes()).thenReturn(mDisplaySizes);
+ if (displaySizes.size() == 2) {
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(p -> p.x * p.y)).get();
+ mUnfolded = getOrientation(largestDisplay);
+ mFolded = getOrientation(smallestDisplay);
+ mUnfoldedRotated = getRotatedOrientation(mUnfolded);
+ mFoldedRotated = getRotatedOrientation(mFolded);
+ }
+ doAnswer(invocation -> getFoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getFoldedOrientation(anyInt());
+ doAnswer(invocation -> getUnfoldedOrientation(invocation.getArgument(0)))
+ .when(mWallpaperDisplayHelper).getUnfoldedOrientation(anyInt());
+ }
+
+ private int getFoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mUnfolded) return mFolded;
+ if (orientation == mUnfoldedRotated) return mFoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ private int getUnfoldedOrientation(int orientation) {
+ if (orientation == ORIENTATION_UNKNOWN) return ORIENTATION_UNKNOWN;
+ if (orientation == mFolded) return mUnfolded;
+ if (orientation == mFoldedRotated) return mUnfoldedRotated;
+ return ORIENTATION_UNKNOWN;
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} successfully removes the parallax in a simple
+ * case, removing the right or left part depending on the "rtl" argument.
+ */
+ @Test
+ public void testNoParallax_noScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1200, 1000);
+ Point expectedCropSize = new Point(1000, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly takes zooming into account.
+ */
+ @Test
+ public void testNoParallax_withScale() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(600, 500);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#noParallax} correctly removes parallax when the image is
+ * cropped, i.e. when the crop rectangle is not the full bitmap.
+ */
+ @Test
+ public void testNoParallax_withScaleAndCrop() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect crop = new Rect(300, 1000, 900, 1500);
+ Point expectedCropSize = new Point(500, 500);
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ false))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.noParallax(crop, displaySize, bitmapSize, /* rtl */ true))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop} does nothing when the crop has the same
+ * width/height ratio than the screen.
+ */
+ @Test
+ public void testGetAdjustedCrop_noOp() {
+ Point displaySize = new Point(1000, 1000);
+
+ for (Point bitmapSize: List.of(
+ new Point(1000, 1000),
+ new Point(2000, 2000),
+ new Point(500, 500))) {
+ for (Rect crop: List.of(
+ new Rect(0, 0, bitmapSize.x, bitmapSize.y),
+ new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) {
+ for (int mode: ALL_MODES) {
+ for (boolean rtl: List.of(true, false)) {
+ for (boolean parallax: List.of(true, false)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, parallax, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not keep more width than needed for {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_tooMuchParallax() {
+ Point displaySize = new Point(1000, 1000);
+ int tooLargeWidth = (int) (displaySize.x * (1 + 2 * WallpaperCropper.MAX_PARALLAX));
+ Point bitmapSize = new Point(tooLargeWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX));
+ Point expectedCropSize = new Point(expectedWidth, 1000);
+ for (int mode: ALL_MODES) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, false, mode))
+ .isEqualTo(leftOf(crop, expectedCropSize));
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, true, mode))
+ .isEqualTo(rightOf(crop, expectedCropSize));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with parallax = true,
+ * does not remove parallax if the parallax is below {@link WallpaperCropper#MAX_PARALLAX}.
+ */
+ @Test
+ public void testGetAdjustedCrop_acceptableParallax() {
+ Point displaySize = new Point(1000, 1000);
+ List<Integer> acceptableWidths = List.of(displaySize.x,
+ (int) (displaySize.x * (1 + 0.5 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 0.9 * WallpaperCropper.MAX_PARALLAX)),
+ (int) (displaySize.x * (1 + 1.0 * WallpaperCropper.MAX_PARALLAX)));
+ for (int acceptableWidth: acceptableWidths) {
+ Point bitmapSize = new Point(acceptableWidth, 1000);
+ Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ for (int mode : ALL_MODES) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, true, rtl, mode))
+ .isEqualTo(crop);
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#ADD}, correctly enlarges the crop to match the display dimensions,
+ * and adds content to the crop by an equal amount on both sides when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_add() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 0, 900, 1000),
+ new Rect(0, 0, 1000, 900),
+ new Rect(0, 0, 400, 500),
+ new Rect(500, 600, 1000, 1000));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 1000, 1000),
+ new Rect(0, 0, 500, 500),
+ new Rect(500, 500, 1000, 1000));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expectedCrop = expectedAdjustedCrops.get(i);
+ for (boolean rtl: List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD))
+ .isEqualTo(expectedCrop);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#REMOVE}, correctly shrinks the crop to match the display dimensions,
+ * and removes content by an equal amount on both sides.
+ */
+ @Test
+ public void testGetAdjustedCrop_remove() {
+ Point displaySize = new Point(1000, 1000);
+ Point bitmapSize = new Point(1500, 1500);
+
+ List<Rect> crops = List.of(
+ new Rect(50, 0, 1150, 1000),
+ new Rect(0, 50, 1000, 1150));
+
+ Point expectedCropSize = new Point(1000, 1000);
+
+ for (Rect crop: crops) {
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE))
+ .isEqualTo(centerOf(crop, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getAdjustedCrop}, when called with
+ * {@link WallpaperCropper#BALANCE}, gives an adjusted crop with the same center and same number
+ * of pixels when possible.
+ */
+ @Test
+ public void testGetAdjustedCrop_balance() {
+ Point displaySize = new Point(500, 1000);
+ Point transposedDisplaySize = new Point(1000, 500);
+ Point bitmapSize = new Point(1000, 1000);
+
+ List<Rect> crops = List.of(
+ new Rect(0, 250, 1000, 750),
+ new Rect(100, 0, 300, 100));
+
+ List<Rect> expectedAdjustedCrops = List.of(
+ new Rect(250, 0, 750, 1000),
+ new Rect(150, 0, 250, 200));
+
+ for (int i = 0; i < crops.size(); i++) {
+ Rect crop = crops.get(i);
+ Rect expected = expectedAdjustedCrops.get(i);
+ assertThat(WallpaperCropper.getAdjustedCrop(
+ crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expected);
+
+ Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right);
+ Rect expectedTransposed = new Rect(
+ expected.top, expected.left, expected.bottom, expected.right);
+ assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize,
+ transposedDisplaySize, false, false, WallpaperCropper.BALANCE))
+ .isEqualTo(expectedTransposed);
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} follows a simple centre-align strategy when
+ * no suggested crops are provided.
+ */
+ @Test
+ public void testGetCrop_noSuggestedCrops_centersWallpaper() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+
+ List<Point> displaySizes = List.of(
+ new Point(500, 1000),
+ new Point(1000, 500));
+ List<Point> expectedCropSizes = List.of(
+ new Point(500, 1000),
+ new Point(800, 400));
+
+ for (int i = 0; i < displaySizes.size(); i++) {
+ Point displaySize = displaySizes.get(i);
+ Point expectedCropSize = expectedCropSizes.get(i);
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ displaySize, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, expectedCropSize));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop} reuses a suggested crop of the same orientation
+ * as the display if possible, and does not remove additional width for parallax,
+ * but adds width if necessary.
+ */
+ @Test
+ public void testGetCrop_hasSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(800, 1000);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
+ for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+ suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
+ }
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(300, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(suggestedCrops.get(PORTRAIT));
+ assertThat(mWallpaperCropper.getCrop(
+ new Point(500, 800), bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(new Rect(0, 0, 500, 800));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, if there is no suggested crop of the same
+ * orientation as the display, reuses a suggested crop of the rotated orientation if possible,
+ * and preserves the center and number of pixels of the crop if possible.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image. Also the image is large enough to preserver the number
+ * of pixels (no additional zoom required).
+ */
+ @Test
+ public void testGetCrop_hasRotatedSuggestedCrop() {
+ setUpWithDisplays(STANDARD_DISPLAY);
+ Point bitmapSize = new Point(2000, 1800);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ Point portrait = PORTRAIT_ONE;
+ Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
+ Point squarePortrait = SQUARE_PORTRAIT_ONE;
+ Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
+ suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
+ suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ landscape, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, landscape));
+
+ assertThat(mWallpaperCropper.getCrop(
+ squarePortrait, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(bitmapRect, squarePortrait));
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for a folded crop with a suggested
+ * crop only for the relative unfolded orientation, creates the folded crop at the center of the
+ * unfolded crop, by removing content on two sides to match the folded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2400);
+ Rect bitmapRect = new Rect(0, 0, bitmapSize.x, bitmapSize.y);
+
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ Rect unfoldedCropOne = centerOf(bitmapRect, mDisplaySizes.get(unfoldedOne));
+ Rect unfoldedCropTwo = centerOf(bitmapRect, mDisplaySizes.get(unfoldedTwo));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfoldedOne, unfoldedCropOne);
+ suggestedCrops.put(unfoldedTwo, unfoldedCropTwo);
+
+ int foldedOne = getFoldedOrientation(unfoldedOne);
+ int foldedTwo = getFoldedOrientation(unfoldedTwo);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayOne, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropOne, foldedDisplayOne));
+
+ assertThat(mWallpaperCropper.getCrop(
+ foldedDisplayTwo, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(unfoldedCropTwo, foldedDisplayTwo));
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the relative folded orientation, creates the unfolded crop with the same center
+ * as the folded crop, by adding content on two sides to match the unfolded screen dimensions.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom) and are
+ * at the center of the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ Point foldedDisplayOne = mDisplaySizes.get(foldedOne);
+ Point foldedDisplayTwo = mDisplaySizes.get(foldedTwo);
+ Rect foldedCropOne = centerOf(bitmapRect, foldedDisplayOne);
+ Rect foldedCropTwo = centerOf(bitmapRect, foldedDisplayTwo);
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(foldedOne, foldedCropOne);
+ suggestedCrops.put(foldedTwo, foldedCropTwo);
+
+ int unfoldedOne = getUnfoldedOrientation(foldedOne);
+ int unfoldedTwo = getUnfoldedOrientation(foldedTwo);
+ Point unfoldedDisplayOne = mDisplaySizes.get(unfoldedOne);
+ Point unfoldedDisplayTwo = mDisplaySizes.get(unfoldedTwo);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayOne, bitmapSize, suggestedCrops, rtl), foldedDisplayOne))
+ .isEqualTo(foldedCropOne);
+
+ assertThat(centerOf(mWallpaperCropper.getCrop(
+ unfoldedDisplayTwo, bitmapSize, suggestedCrops, rtl), foldedDisplayTwo))
+ .isEqualTo(foldedCropTwo);
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an folded crop with a suggested
+ * crop only for the rotated unfolded orientation, creates the folded crop from that crop by
+ * combining a rotate + fold operation. The folded crop should have less pixels than the
+ * unfolded crop due to the fold operation which removes content on both sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are at the center of the image.
+ */
+ @Test
+ public void testGetCrop_hasRotatedUnfoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+ Point largestDisplay = displaySizes.stream().max(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int unfoldedOne = getOrientation(largestDisplay);
+ int unfoldedTwo = getRotatedOrientation(unfoldedOne);
+ for (int unfolded: List.of(unfoldedOne, unfoldedTwo)) {
+ Rect unfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(unfolded));
+ int rotatedUnfolded = getRotatedOrientation(unfolded);
+ Rect rotatedUnfoldedCrop = centerOf(bitmapRect, mDisplaySizes.get(rotatedUnfolded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(unfolded, unfoldedCrop);
+ int rotatedFolded = getFoldedOrientation(rotatedUnfolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ assertThat(mWallpaperCropper.getCrop(
+ rotatedFoldedDisplay, bitmapSize, suggestedCrops, rtl))
+ .isEqualTo(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay));
+ }
+ }
+ }
+ }
+
+ /**
+ * Test that {@link WallpaperCropper#getCrop}, when asked for an unfolded crop with a suggested
+ * crop only for the rotated folded orientation, creates the unfolded crop from that crop by
+ * combining a rotate + unfold operation. The unfolded crop should have more pixels than the
+ * folded crop due to the unfold operation which adds content on two sides of the image.
+ * <p>
+ * To simplify, in this test case all crops have the same size as the display (no zoom)
+ * and are centered inside the image. Also the image is large enough to add content.
+ */
+ @Test
+ public void testGetCrop_hasRotatedFoldedSuggestedCrop() {
+ for (List<Point> displaySizes : ALL_FOLDABLE_DISPLAYS) {
+ setUpWithDisplays(displaySizes);
+ Point bitmapSize = new Point(2000, 2000);
+ Rect bitmapRect = new Rect(0, 0, 2000, 2000);
+
+ Point smallestDisplay = displaySizes.stream().min(
+ Comparator.comparingInt(a -> a.x * a.y)).orElseThrow();
+ int foldedOne = getOrientation(smallestDisplay);
+ int foldedTwo = getRotatedOrientation(foldedOne);
+ for (int folded: List.of(foldedOne, foldedTwo)) {
+ Rect foldedCrop = centerOf(bitmapRect, mDisplaySizes.get(folded));
+ SparseArray<Rect> suggestedCrops = new SparseArray<>();
+ suggestedCrops.put(folded, foldedCrop);
+ int rotatedFolded = getRotatedOrientation(folded);
+ int rotatedUnfolded = getUnfoldedOrientation(rotatedFolded);
+ Point rotatedFoldedDisplay = mDisplaySizes.get(rotatedFolded);
+ Rect rotatedFoldedCrop = centerOf(bitmapRect, rotatedFoldedDisplay);
+ Point rotatedUnfoldedDisplay = mDisplaySizes.get(rotatedUnfolded);
+
+ for (boolean rtl : List.of(false, true)) {
+ Rect rotatedUnfoldedCrop = mWallpaperCropper.getCrop(
+ rotatedUnfoldedDisplay, bitmapSize, suggestedCrops, rtl);
+ assertThat(centerOf(rotatedUnfoldedCrop, rotatedFoldedDisplay))
+ .isEqualTo(rotatedFoldedCrop);
+ }
+ }
+ }
+ }
+
+ private static Rect centerOf(Rect container, Point point) {
+ checkSubset(container, point);
+ int diffWidth = container.width() - point.x;
+ int diffHeight = container.height() - point.y;
+ int startX = container.left + diffWidth / 2;
+ int startY = container.top + diffHeight / 2;
+ return new Rect(startX, startY, startX + point.x, startY + point.y);
+ }
+
+ private static Rect leftOf(Rect container, Point point) {
+ Rect result = centerOf(container, point);
+ result.offset(container.left - result.left, 0);
+ return result;
+ }
+
+ private static Rect rightOf(Rect container, Point point) {
+ checkSubset(container, point);
+ Rect result = centerOf(container, point);
+ result.offset(container.right - result.right, 0);
+ return result;
+ }
+
+ private static void checkSubset(Rect container, Point point) {
+ if (container.width() < point.x || container.height() < point.y) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 5e5181b..0089d4c 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -109,7 +109,6 @@
<uses-permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_CONTEXT_HUB" />
<uses-permission android:name="android.permission.USE_BIOMETRIC_INTERNAL" />
- <uses-permission android:name="android.permission.USE_BACKGROUND_FACE_AUTHENTICATION" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
<uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
index 4db27d2..dc26e6e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerWithAccessibilityWindowTest.java
@@ -17,9 +17,9 @@
package com.android.server.accessibility;
import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
-import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.DisplayIdMatcher.displayId;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowChangesMatcher.a11yWindowChanges;
+import static com.android.server.accessibility.AccessibilityWindowManagerWithAccessibilityWindowTest.WindowIdMatcher.a11yWindowId;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -27,11 +27,13 @@
import static junit.framework.Assert.assertTrue;
import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +44,7 @@
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
+import android.graphics.Point;
import android.graphics.Region;
import android.os.IBinder;
import android.os.LocaleList;
@@ -63,6 +66,7 @@
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
import com.android.server.accessibility.test.MessageCapturingHandler;
+import com.android.server.wm.AccessibilityWindowsPopulator.AccessibilityWindow;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
@@ -117,9 +121,8 @@
// List of window token, mapping from windowId -> window token.
private final SparseArray<IWindow> mA11yWindowTokens = new SparseArray<>();
- // List of window info lists, mapping from displayId -> window info lists.
- private final SparseArray<ArrayList<WindowInfo>> mWindowInfos =
- new SparseArray<>();
+ // List of window info lists, mapping from displayId -> a11y window lists.
+ private final SparseArray<ArrayList<AccessibilityWindow>> mWindows = new SparseArray<>();
// List of callback, mapping from displayId -> callback.
private final SparseArray<WindowsForAccessibilityCallback> mCallbackOfWindows =
new SparseArray<>();
@@ -129,6 +132,13 @@
private final MessageCapturingHandler mHandler = new MessageCapturingHandler(null);
+ // This maps displayId -> next region offset.
+ // Touchable region must have un-occluded area so that it's exposed to a11y services.
+ // This offset can be used as left and top of new region so that top-left of each region are
+ // kept visible.
+ // It's expected to be incremented by some amount everytime the value is used.
+ private final SparseArray<Integer> mNextRegionOffsets = new SparseArray<>();
+
@Mock
private WindowManagerInternal mMockWindowManagerInternal;
@Mock
@@ -162,7 +172,7 @@
anyString(), anyInt(), anyInt(), anyInt())).thenReturn(PACKAGE_NAME);
doAnswer((invocation) -> {
- onWindowsForAccessibilityChanged(invocation.getArgument(0), false);
+ onAccessibilityWindowsChanged(invocation.getArgument(0), false);
return null;
}).when(mMockWindowManagerInternal).computeWindowsForAccessibility(anyInt());
@@ -176,7 +186,7 @@
// as top focused display before each testing starts.
startTrackingPerDisplay(Display.DEFAULT_DISPLAY);
- // AccessibilityEventSender is invoked during onWindowsForAccessibilityChanged.
+ // AccessibilityEventSender is invoked during onAccessibilityWindowsChanged.
// Resets it for mockito verify of further test case.
Mockito.reset(mMockA11yEventSender);
@@ -240,19 +250,18 @@
@Test
public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
- WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
+ final WindowInfo focusedWindowInfo =
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, focusedWindowInfo.token));
focusedWindowInfo.focused = false;
- focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1);
- focusedWindowInfo.focused = true;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().focused = true;
mA11yWindowManager.onTouchInteractionStart();
setTopFocusedWindowAndDisplay(Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX + 1);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
}
@@ -276,7 +285,7 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX + 1, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should not be changed.
assertEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should not be changed.
@@ -304,8 +313,8 @@
changeFocusedWindowOnDisplayPerDisplayFocusConfig(SECONDARY_DISPLAY_ID,
DEFAULT_FOCUSED_INDEX, Display.DEFAULT_DISPLAY, DEFAULT_FOCUSED_INDEX);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- onWindowsForAccessibilityChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(SECONDARY_DISPLAY_ID, SEND_ON_WINDOW_CHANGES);
// The active window should be changed.
assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
// The top focused window should be changed.
@@ -315,53 +324,43 @@
@Test
public void onWindowsChanged_shouldReportCorrectLayer() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
- assertThat(mWindowInfos.get(Display.DEFAULT_DISPLAY).size() - windowInfo.layer - 1,
+ assertThat(mWindows.get(Display.DEFAULT_DISPLAY).size() - i - 1,
is(a11yWindow.getLayer()));
}
}
@Test
public void onWindowsChanged_shouldReportCorrectOrder() {
- // AccessibilityWindowManager#onWindowsForAccessibilityChanged already invoked in setup.
+ // AccessibilityWindowManager#onAccessibilityWindowsChanged already invoked in setup.
List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
for (int i = 0; i < a11yWindows.size(); i++) {
final AccessibilityWindowInfo a11yWindow = a11yWindows.get(i);
final IBinder windowToken = mA11yWindowManager
.getWindowTokenForUserAndWindowIdLocked(USER_SYSTEM_ID, a11yWindow.getId());
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(i);
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(i).getWindowInfo();
assertThat(windowToken, is(windowInfo.token));
}
}
@Test
public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
+ assertNotEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
- assertNotEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
- }
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().title = "new title";
- @Test
- public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- final int correctLayer =
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer();
- windowInfo.layer += 1;
-
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
- assertEquals(correctLayer,
- mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0).getLayer());
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ assertEquals("new title",
+ toString(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY)
+ .get(0).getTitle()));
}
@Test
@@ -371,14 +370,10 @@
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
true, USER_SYSTEM_ID);
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = token.asBinder();
- windowInfo.layer = 0;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- mWindowInfos.get(Display.DEFAULT_DISPLAY).set(0, windowInfo);
+ mWindows.get(Display.DEFAULT_DISPLAY).set(0,
+ createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotEquals(oldWindow,
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0));
}
@@ -386,12 +381,12 @@
@Test
public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
final WindowInfo focusedWindowInfo =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX);
- final WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).getWindowInfo();
+ final WindowInfo windowInfo = mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo();
focusedWindowInfo.focused = false;
windowInfo.focused = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertTrue(mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY).get(0)
.isFocused());
}
@@ -500,15 +495,18 @@
@Test
public void computePartialInteractiveRegionForWindow_wholeVisible_returnWholeRegion() {
// Updates top 2 z-order WindowInfo are whole visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
- windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1);
- windowInfo.regionInScreen.set(0, SCREEN_HEIGHT / 2,
- SCREEN_WIDTH, SCREEN_HEIGHT);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(0).getId();
@@ -526,12 +524,17 @@
@Test
public void computePartialInteractiveRegionForWindow_halfVisible_returnHalfRegion() {
// Updates z-order #1 WindowInfo is half visible.
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2));
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -542,9 +545,17 @@
@Test
public void computePartialInteractiveRegionForWindow_notVisible_returnEmptyRegion() {
- // Since z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ // z-order #0 WindowInfo is full screen, z-order #1 WindowInfo should be invisible.
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ // Note that the second window is also exposed even if region is empty because it's focused.
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
int windowId = a11yWindows.get(1).getId();
@@ -555,16 +566,21 @@
@Test
public void computePartialInteractiveRegionForWindow_partialVisible_returnVisibleRegion() {
// Updates z-order #0 WindowInfo to have two interact-able areas.
- Region region = new Region(0, 0, SCREEN_WIDTH, 200);
+ final Region region = new Region(0, 0, SCREEN_WIDTH, 200);
region.op(0, SCREEN_HEIGHT - 200, SCREEN_WIDTH, SCREEN_HEIGHT, Region.Op.UNION);
- WindowInfo windowInfo = mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0);
- windowInfo.regionInScreen.set(region);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ final AccessibilityWindow firstWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(0);
+ setRegionForMockAccessibilityWindow(firstWindow, region);
+ final AccessibilityWindow secondWindow = mWindows.get(Display.DEFAULT_DISPLAY).get(1);
+ setRegionForMockAccessibilityWindow(secondWindow,
+ new Region(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT));
+
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
final List<AccessibilityWindowInfo> a11yWindows =
mA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY);
+ assertThat(a11yWindows, hasSize(2));
final Region outBounds = new Region();
- int windowId = a11yWindows.get(1).getId();
+ final int windowId = a11yWindows.get(1).getId();
mA11yWindowManager.computePartialInteractiveRegionForWindowLocked(windowId, outBounds);
assertFalse(outBounds.getBounds().isEmpty());
@@ -575,7 +591,8 @@
@Test
public void updateActiveAndA11yFocusedWindow_windowStateChangedEvent_noTracking_shouldUpdate() {
final IBinder eventWindowToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX + 1).token;
+ mWindows.get(Display.DEFAULT_DISPLAY)
+ .get(DEFAULT_FOCUSED_INDEX + 1).getWindowInfo().token;
final int eventWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, eventWindowToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -766,7 +783,8 @@
public void onTouchInteractionEnd_noServiceInteractiveWindow_shouldClearA11yFocus()
throws RemoteException {
final IBinder defaultFocusWinToken =
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(DEFAULT_FOCUSED_INDEX).token;
+ mWindows.get(Display.DEFAULT_DISPLAY).get(
+ DEFAULT_FOCUSED_INDEX).getWindowInfo().token;
final int defaultFocusWindowId = mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, defaultFocusWinToken);
when(mMockWindowManagerInternal.getFocusedWindowTokenFromWindowStates())
@@ -811,8 +829,8 @@
@Test
public void getPictureInPictureWindow_shouldNotNull() {
assertNull(mA11yWindowManager.getPictureInPictureWindowLocked());
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(1).inPictureInPicture = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(1).getWindowInfo().inPictureInPicture = true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
assertNotNull(mA11yWindowManager.getPictureInPictureWindowLocked());
}
@@ -826,8 +844,9 @@
final IAccessibilityInteractionConnection mockRemoteConnection =
mA11yWindowManager.getConnectionLocked(
USER_SYSTEM_ID, outsideWindowId).getRemote();
- mWindowInfos.get(Display.DEFAULT_DISPLAY).get(0).hasFlagWatchOutsideTouch = true;
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
+ mWindows.get(Display.DEFAULT_DISPLAY).get(0).getWindowInfo().hasFlagWatchOutsideTouch =
+ true;
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, SEND_ON_WINDOW_CHANGES);
mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
verify(mockRemoteConnection).notifyOutsideTouch();
@@ -945,18 +964,14 @@
@Test
public void sendAccessibilityEventOnWindowRemoval() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
// Removing index 0 because it's not focused, and avoids unnecessary layer change.
final int windowId =
getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- infos.remove(0);
- for (WindowInfo info : infos) {
- // Adjust layer number because it should start from 0.
- info.layer--;
- }
+ windows.remove(0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -970,21 +985,15 @@
@Test
public void sendAccessibilityEventOnWindowAddition() throws RemoteException {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
-
- for (WindowInfo info : infos) {
- // Adjust layer number because new window will have 0 so that layer number in
- // A11yWindowInfo in window won't be changed.
- info.layer++;
- }
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
final IWindow token = addAccessibilityInteractionConnection(Display.DEFAULT_DISPLAY,
false, USER_SYSTEM_ID);
- addWindowInfo(infos, token, 0);
- final int windowId =
- getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, infos.size() - 1);
+ // Adding window to the front so that other windows' layer won't change.
+ windows.add(0, createMockAccessibilityWindow(token, Display.DEFAULT_DISPLAY));
+ final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -998,11 +1007,11 @@
@Test
public void sendAccessibilityEventOnWindowChange() {
- final ArrayList<WindowInfo> infos = mWindowInfos.get(Display.DEFAULT_DISPLAY);
- infos.get(0).title = "new title";
+ final ArrayList<AccessibilityWindow> windows = mWindows.get(Display.DEFAULT_DISPLAY);
+ windows.get(0).getWindowInfo().title = "new title";
final int windowId = getWindowIdFromWindowInfosForDisplay(Display.DEFAULT_DISPLAY, 0);
- onWindowsForAccessibilityChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
+ onAccessibilityWindowsChanged(Display.DEFAULT_DISPLAY, FORCE_SEND);
final ArgumentCaptor<AccessibilityEvent> captor =
ArgumentCaptor.forClass(AccessibilityEvent.class);
@@ -1020,42 +1029,41 @@
}
private void startTrackingPerDisplay(int displayId) throws RemoteException {
- ArrayList<WindowInfo> windowInfosForDisplay = new ArrayList<>();
+ ArrayList<AccessibilityWindow> windowsForDisplay = new ArrayList<>();
// Adds RemoteAccessibilityConnection into AccessibilityWindowManager, and copy
// mock window token into mA11yWindowTokens. Also, preparing WindowInfo mWindowInfos
// for the test.
- int layer = 0;
for (int i = 0; i < NUM_GLOBAL_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
true, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
for (int i = 0; i < NUM_APP_WINDOWS; i++) {
final IWindow token = addAccessibilityInteractionConnection(displayId,
false, USER_SYSTEM_ID);
- addWindowInfo(windowInfosForDisplay, token, layer++);
+ windowsForDisplay.add(createMockAccessibilityWindow(token, displayId));
}
// Sets up current focused window of display.
// Each display has its own current focused window if config_perDisplayFocusEnabled is true.
// Otherwise only default display needs to current focused window.
if (mSupportPerDisplayFocus || displayId == Display.DEFAULT_DISPLAY) {
- windowInfosForDisplay.get(DEFAULT_FOCUSED_INDEX).focused = true;
+ windowsForDisplay.get(DEFAULT_FOCUSED_INDEX).getWindowInfo().focused = true;
}
// Turns on windows tracking, and update window info.
mA11yWindowManager.startTrackingWindows(displayId, false);
// Puts window lists into array.
- mWindowInfos.put(displayId, windowInfosForDisplay);
+ mWindows.put(displayId, windowsForDisplay);
// Sets the default display is the top focused display and
// its current focused window is the top focused window.
if (displayId == Display.DEFAULT_DISPLAY) {
setTopFocusedWindowAndDisplay(displayId, DEFAULT_FOCUSED_INDEX);
}
// Invokes callback for sending window lists to A11y framework.
- onWindowsForAccessibilityChanged(displayId, FORCE_SEND);
+ onAccessibilityWindowsChanged(displayId, FORCE_SEND);
assertEquals(mA11yWindowManager.getWindowListLocked(displayId).size(),
- windowInfosForDisplay.size());
+ windowsForDisplay.size());
}
private WindowsForAccessibilityCallback getWindowsForAccessibilityCallbacks(int displayId) {
@@ -1109,36 +1117,28 @@
return windowId;
}
- private void addWindowInfo(ArrayList<WindowInfo> windowInfos, IWindow windowToken, int layer) {
- final WindowInfo windowInfo = WindowInfo.obtain();
- windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
- windowInfo.token = windowToken.asBinder();
- windowInfo.layer = layer;
- windowInfo.regionInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- windowInfos.add(windowInfo);
- }
-
private int getWindowIdFromWindowInfosForDisplay(int displayId, int index) {
- final IBinder windowToken = mWindowInfos.get(displayId).get(index).token;
+ final IBinder windowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
return mA11yWindowManager.findWindowIdLocked(
USER_SYSTEM_ID, windowToken);
}
private void setTopFocusedWindowAndDisplay(int displayId, int index) {
// Sets the top focus window.
- mTopFocusedWindowToken = mWindowInfos.get(displayId).get(index).token;
+ mTopFocusedWindowToken = mWindows.get(displayId).get(index).getWindowInfo().token;
// Sets the top focused display.
mTopFocusedDisplayId = displayId;
}
- private void onWindowsForAccessibilityChanged(int displayId, boolean forceSend) {
+ private void onAccessibilityWindowsChanged(int displayId, boolean forceSend) {
WindowsForAccessibilityCallback callbacks = mCallbackOfWindows.get(displayId);
if (callbacks == null) {
callbacks = getWindowsForAccessibilityCallbacks(displayId);
mCallbackOfWindows.put(displayId, callbacks);
}
- callbacks.onWindowsForAccessibilityChanged(forceSend, mTopFocusedDisplayId,
- mTopFocusedWindowToken, mWindowInfos.get(displayId));
+ callbacks.onAccessibilityWindowsChanged(forceSend, mTopFocusedDisplayId,
+ mTopFocusedWindowToken, new Point(SCREEN_WIDTH, SCREEN_HEIGHT),
+ mWindows.get(displayId));
}
private void changeFocusedWindowOnDisplayPerDisplayFocusConfig(
@@ -1147,23 +1147,23 @@
if (mSupportPerDisplayFocus) {
// Gets the old focused window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Gets the new window of display which wants to change focused window.
focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
} else {
// Gets the window of display which wants to change focused window.
WindowInfo focusedWindowInfo =
- mWindowInfos.get(changeFocusedDisplayId).get(newFocusedWindowIndex);
+ mWindows.get(changeFocusedDisplayId).get(newFocusedWindowIndex).getWindowInfo();
// Sets the focus of new focused window.
focusedWindowInfo.focused = true;
// Gets the old focused window of old top focused display.
focusedWindowInfo =
- mWindowInfos.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex);
+ mWindows.get(oldTopFocusedDisplayId).get(oldFocusedWindowIndex).getWindowInfo();
// Resets the focus of old focused window.
focusedWindowInfo.focused = false;
// Changes the top focused display and window.
@@ -1171,6 +1171,42 @@
}
}
+ private AccessibilityWindow createMockAccessibilityWindow(IWindow windowToken, int displayId) {
+ final WindowInfo windowInfo = WindowInfo.obtain();
+ // TODO(b/325341171): add tests with various kinds of windows such as
+ // changing window types, touchable or not, trusted or not, etc.
+ windowInfo.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+ windowInfo.token = windowToken.asBinder();
+
+ final AccessibilityWindow window = Mockito.mock(AccessibilityWindow.class);
+ when(window.getWindowInfo()).thenReturn(windowInfo);
+ when(window.ignoreRecentsAnimationForAccessibility()).thenReturn(false);
+ when(window.isFocused()).thenAnswer(invocation -> windowInfo.focused);
+ when(window.isTouchable()).thenReturn(true);
+ when(window.getType()).thenReturn(windowInfo.type);
+
+ setRegionForMockAccessibilityWindow(window, nextToucableRegion(displayId));
+ return window;
+ }
+
+ private void setRegionForMockAccessibilityWindow(AccessibilityWindow window, Region region) {
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInScreen(any(Region.class));
+ doAnswer(invocation -> {
+ ((Region) invocation.getArgument(0)).set(region);
+ return null;
+ }).when(window).getTouchableRegionInWindow(any(Region.class));
+ }
+
+ private Region nextToucableRegion(int displayId) {
+ final int topLeft = mNextRegionOffsets.get(displayId, 0);
+ final int bottomRight = topLeft + 100;
+ mNextRegionOffsets.put(displayId, topLeft + 10);
+ return new Region(topLeft, topLeft, bottomRight, bottomRight);
+ }
+
@Nullable
private static String toString(@Nullable CharSequence cs) {
return cs == null ? null : cs.toString();
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 8c0d44c..7fbd521 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -189,6 +189,8 @@
FullScreenMagnificationVibrationHelper mMockFullScreenMagnificationVibrationHelper;
@Mock
FullScreenMagnificationGestureHandler.MagnificationLogger mMockMagnificationLogger;
+ @Mock
+ OneFingerPanningSettingsProvider mMockOneFingerPanningSettingsProvider;
@Rule
public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
@@ -266,6 +268,7 @@
mMgh.onDestroy();
mFullScreenMagnificationController.unregister(DISPLAY_0);
verify(mWindowMagnificationPromptController).onDestroy();
+ verify(mMockOneFingerPanningSettingsProvider).unregister();
Settings.Secure.putFloatForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
mOriginalMagnificationPersistedScale,
@@ -288,11 +291,10 @@
DISPLAY_0,
mMockFullScreenMagnificationVibrationHelper,
mMockMagnificationLogger,
- ViewConfiguration.get(mContext));
+ ViewConfiguration.get(mContext),
+ mMockOneFingerPanningSettingsProvider);
if (isWatch()) {
- h.setSinglePanningEnabled(true);
- } else {
- h.setSinglePanningEnabled(false);
+ enableOneFingerPanning(true);
}
mHandler = new TestHandler(h.mDetectingState, mClock) {
@Override
@@ -607,8 +609,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testTwoFingerTap_StateIsActivated_shouldInDelegating() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
allowEventDelegation();
@@ -623,8 +625,8 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
public void testTwoFingerTap_StateIsIdle_shouldInDelegating() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_IDLE);
allowEventDelegation();
@@ -830,7 +832,7 @@
@Test
public void testActionUpNotAtEdge_singlePanningState_detectingState() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
send(upEvent());
@@ -841,8 +843,8 @@
@Test
public void testScroll_SinglePanningDisabled_delegatingState() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
- mMgh.setSinglePanningEnabled(false);
+ assumeTrue(isWatch());
+ enableOneFingerPanning(false);
goFromStateIdleTo(STATE_ACTIVATED);
allowEventDelegation();
@@ -854,7 +856,7 @@
@Test
@FlakyTest
public void testScroll_singleHorizontalPanningAndAtEdge_leftEdgeOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -878,7 +880,7 @@
@Test
@FlakyTest
public void testScroll_singleHorizontalPanningAndAtEdge_rightEdgeOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -902,7 +904,7 @@
@Test
@FlakyTest
public void testScroll_singleVerticalPanningAndAtEdge_verticalOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerX =
(INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
@@ -924,7 +926,7 @@
@Test
public void testScroll_singlePanningAndAtEdge_noOverscroll() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
float centerY =
(INITIAL_MAGNIFICATION_BOUNDS.top + INITIAL_MAGNIFICATION_BOUNDS.bottom) / 2.0f;
@@ -946,7 +948,7 @@
@Test
public void testScroll_singleHorizontalPanningAndAtEdge_vibrate() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
DISPLAY_0,
@@ -970,7 +972,7 @@
@Test
public void testScroll_singleVerticalPanningAndAtEdge_doNotVibrate() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
mFullScreenMagnificationController.setCenter(
DISPLAY_0,
@@ -993,8 +995,9 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_FULLSCREEN_FLING_GESTURE)
public void singleFinger_testScrollAfterMagnified_startsFling() {
- assumeTrue(mMgh.mIsSinglePanningEnabled);
+ assumeTrue(isWatch());
goFromStateIdleTo(STATE_ACTIVATED);
swipeAndHold();
@@ -1274,6 +1277,10 @@
mFullScreenMagnificationController.reset(DISPLAY_0, /* animate= */ false);
}
+ private void enableOneFingerPanning(boolean enable) {
+ when(mMockOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()).thenReturn(enable);
+ }
+
private void assertActionsInOrder(List<MotionEvent> actualEvents,
List<Integer> expectedActions) {
assertTrue(actualEvents.size() == expectedActions.size());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
new file mode 100644
index 0000000..ac46ef9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/OneFingerPanningSettingsProviderTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.magnification;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.accessibility.magnification.OneFingerPanningSettingsProvider.State;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class OneFingerPanningSettingsProviderTest {
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(getInstrumentation().getContext());
+
+ private boolean mDefaultValue;
+ private boolean mOriginalIsOneFingerPanningEnabled;
+
+ private OneFingerPanningSettingsProvider mProvider;
+
+ @Before
+ public void setup() {
+ mDefaultValue = OneFingerPanningSettingsProvider.isOneFingerPanningEnabledDefault(mContext);
+ mOriginalIsOneFingerPanningEnabled = isSecureSettingsEnabled();
+ }
+
+ @After
+ public void tearDown() {
+ enableSecureSettings(mOriginalIsOneFingerPanningEnabled);
+ if (mProvider != null) {
+ mProvider.unregister();
+ }
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagDisabled_matchesDefault() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingEnabled_true() {
+ enableSecureSettings(true);
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ assertTrue(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingDisabled_false() {
+ enableSecureSettings(false);
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ assertFalse(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingsFalse_false() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ // Simulate observer triggered.
+ enableSecureSettings(false);
+ mProvider.mObserver.onChange(/* selfChange= */ false);
+
+ assertFalse(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagEnabledSettingsTrue_true() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ true);
+
+ // Simulate observer triggered.
+ enableSecureSettings(true);
+ mProvider.mObserver.onChange(/* selfChange= */ false);
+
+ assertTrue(mProvider.isOneFingerPanningEnabled());
+ }
+
+ @Test
+ public void isOneFingerPanningEnabled_flagDisabledSettingsChanges_valueUnchanged() {
+ mProvider = new OneFingerPanningSettingsProvider(mContext, /* featureFlagEnabled */ false);
+ var previousValue = mProvider.isOneFingerPanningEnabled();
+
+ enableSecureSettings(!previousValue);
+
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(previousValue);
+ assertThat(mProvider.isOneFingerPanningEnabled()).isEqualTo(mDefaultValue);
+ }
+
+ @Test
+ public void unregister_featureEnabled_contentResolverNull() {
+ var provider = new OneFingerPanningSettingsProvider(
+ mContext, /* featureFlagEnabled */ true);
+
+ provider.unregister();
+
+ assertThat(provider.mContentResolver).isNull();
+ }
+
+ @Test
+ public void unregister_featureDisabled_noError() {
+ var provider = new OneFingerPanningSettingsProvider(
+ mContext, /* featureFlagEnabled */ false);
+
+ provider.unregister();
+ }
+
+ private void enableSecureSettings(boolean enable) {
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ OneFingerPanningSettingsProvider.KEY,
+ enable ? State.ON : State.OFF,
+ mContext.getUserId());
+ }
+
+ private boolean isSecureSettingsEnabled() {
+ return State.ON == Settings.Secure.getIntForUser(
+ mContext.getContentResolver(),
+ OneFingerPanningSettingsProvider.KEY,
+ mDefaultValue ? State.ON : State.OFF,
+ mContext.getUserId());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index cea10ea..ea1a68a 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -825,7 +825,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true);
verifyUserUnassignedFromDisplay(TEST_USER_ID1);
@@ -842,7 +843,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ false);
@@ -852,19 +854,28 @@
public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception {
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
- mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.disableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
- mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
mSetFlagsRule.enableFlags(
android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
+
+ mSetFlagsRule.disableFlags(android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
+ mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ setUpAndStartProfileInBackground(TEST_USER_ID3, UserManager.USER_TYPE_PROFILE_PRIVATE);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* allowDelayedLocking= */ true,
+ /* keyEvictedCallback */ null, /* expectLocking= */ true);
}
/** Delayed-locking users (as opposed to devices) have no limits on how many can be unlocked. */
@@ -874,7 +885,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE);
setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
@@ -890,7 +902,8 @@
mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true,
/* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE,
- android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE);
+ android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED);
assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* allowDelayedLocking= */ true,
/* keyEvictedCallback */ null, /* expectLocking= */ true);
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
new file mode 100644
index 0000000..79766f8
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appwidget/ApiCounterTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.appwidget
+
+import android.content.ComponentName
+import com.android.server.appwidget.AppWidgetServiceImpl.ApiCounter
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class ApiCounterTest {
+ private companion object {
+ const val RESET_INTERVAL_MS = 10L
+ const val MAX_CALLS_PER_INTERVAL = 2
+ }
+
+ private var currentTime = 0L
+
+ private val id =
+ AppWidgetServiceImpl.ProviderId(
+ /* uid= */ 123,
+ ComponentName("com.android.server.appwidget", "FakeProviderClass")
+ )
+ private val counter = ApiCounter(RESET_INTERVAL_MS, MAX_CALLS_PER_INTERVAL) { currentTime }
+
+ @Test
+ fun tryApiCall() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 5L
+ assertThat(counter.tryApiCall(id)).isFalse()
+ currentTime = 11L
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+
+ @Test
+ fun remove() {
+ for (i in 0 until MAX_CALLS_PER_INTERVAL) {
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+ assertThat(counter.tryApiCall(id)).isFalse()
+ // remove should cause the call count to be 0 on the next tryApiCall
+ counter.remove(id)
+ assertThat(counter.tryApiCall(id)).isTrue()
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
index bb00634..fa1fd90 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/ALSProbeTest.java
@@ -344,6 +344,21 @@
verifyNoMoreInteractions(mSensorManager);
}
+ @Test
+ public void testAwaitLuxWhenNoLightSensor() {
+ when(mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)).thenReturn(null);
+ mProbe = new ALSProbe(mSensorManager, new Handler(mLooper.getLooper()), TIMEOUT_MS - 1);
+
+ AtomicInteger lux = new AtomicInteger(-5);
+ mProbe.awaitNextLux((v) -> lux.set(Math.round(v)), null /* handler */);
+
+ // Verify that no light sensor will be registered.
+ verify(mSensorManager, times(0)).registerListener(
+ mSensorEventListenerCaptor.capture(), any(), anyInt());
+
+ assertThat(lux.get()).isEqualTo(-1);
+ }
+
private void moveTimeBy(long millis) {
mLooper.moveTimeForward(millis);
mLooper.processAllMessages();
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
index c8a5583d..3aaac2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceTest.java
@@ -16,7 +16,6 @@
package com.android.server.biometrics.sensors.face;
-import static android.Manifest.permission.USE_BACKGROUND_FACE_AUTHENTICATION;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceSensorProperties.TYPE_UNKNOWN;
@@ -235,26 +234,6 @@
}
@Test
- public void testAuthenticateInBackground() throws Exception {
- FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
- .build();
- initService();
- mFaceService.mServiceWrapper.registerAuthenticators(List.of());
- waitForRegistration();
-
- mContext.getTestablePermissions().setPermission(
- USE_BIOMETRIC_INTERNAL, PackageManager.PERMISSION_DENIED);
- mContext.getTestablePermissions().setPermission(
- USE_BACKGROUND_FACE_AUTHENTICATION, PackageManager.PERMISSION_GRANTED);
-
- final long operationId = 5;
- mFaceService.mServiceWrapper.authenticateInBackground(mToken, operationId,
- mFaceServiceReceiver, faceAuthenticateOptions);
-
- assertThat(faceAuthenticateOptions.getSensorId()).isEqualTo(ID_DEFAULT);
- }
-
- @Test
public void testOptionsForDetect() throws Exception {
FaceAuthenticateOptions faceAuthenticateOptions = new FaceAuthenticateOptions.Builder()
.setOpPackageName(ComponentName.unflattenFromString(OP_PACKAGE_NAME)
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
index 9ad2652..6731403 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseTvToAudioSystemAvbTest.java
@@ -235,7 +235,7 @@
}
@Test
- public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_doesNotSendSetAudioVolumeLevel() {
+ public void adjustOnlyAvbEnabled_audioDeviceVolumeChanged_requestsAndUpdatesAudioStatus() {
enableAdjustOnlyAbsoluteVolumeBehavior();
mNativeWrapper.clearResultMessages();
@@ -250,7 +250,22 @@
);
mTestLooper.dispatchAll();
- assertThat(mNativeWrapper.getResultMessages()).isEmpty();
+ // We can't sent <Set Audio Volume Level> when using adjust-only AVB.
+ // Instead, we send <Give Audio Status>, to get the System Audio device's volume level.
+ // This ensures that we end up with a correct audio status in AudioService, even if it
+ // set it incorrectly because it assumed that we could send <Set Audio Volume Level>
+ assertThat(mNativeWrapper.getResultMessages().size()).isEqualTo(1);
+ assertThat(mNativeWrapper.getResultMessages()).contains(
+ HdmiCecMessageBuilder.buildGiveAudioStatus(getLogicalAddress(),
+ getSystemAudioDeviceLogicalAddress())
+ );
+
+ // When we receive <Report Audio Status>, we notify AudioService of the volume level.
+ receiveReportAudioStatus(50,
+ true);
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(50 * STREAM_MUSIC_MAX_VOLUME / AudioStatus.MAX_VOLUME),
+ anyInt());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
index 0aa72d0..98e119c 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java
@@ -184,13 +184,13 @@
@Test
public void isValid_setMenuLanguage() {
- assertMessageValidity("4F:32:53:50:41").isEqualTo(OK);
+ assertMessageValidity("0F:32:53:50:41").isEqualTo(OK);
assertMessageValidity("0F:32:45:4E:47:8C:49:D3:48").isEqualTo(OK);
- assertMessageValidity("40:32:53:50:41").isEqualTo(ERROR_DESTINATION);
- assertMessageValidity("F0:32").isEqualTo(ERROR_SOURCE);
- assertMessageValidity("4F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
- assertMessageValidity("4F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
+ assertMessageValidity("04:32:53:50:41").isEqualTo(ERROR_DESTINATION);
+ assertMessageValidity("40:32").isEqualTo(ERROR_SOURCE);
+ assertMessageValidity("0F:32:45:55").isEqualTo(ERROR_PARAMETER_SHORT);
+ assertMessageValidity("0F:32:19:7F:83").isEqualTo(ERROR_PARAMETER);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
index e0ef035..a6f2196 100644
--- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java
@@ -30,6 +30,7 @@
import android.app.role.RoleManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.BugreportManager.BugreportCallback;
import android.os.BugreportParams;
@@ -37,6 +38,7 @@
import android.os.IDumpstateListener;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -67,6 +69,9 @@
@RunWith(AndroidJUnit4.class)
public class BugreportManagerServiceImplTest {
+ private static final UserInfo ADMIN_USER_INFO =
+ new UserInfo(/* id= */ 5678, "adminUser", UserInfo.FLAG_ADMIN);
+
@Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -82,6 +87,8 @@
@Mock
private DevicePolicyManager mMockDevicePolicyManager;
+ private TestInjector mInjector;
+
private int mCallingUid = 1234;
private String mCallingPackage = "test.package";
private AtomicFile mMappingFile;
@@ -96,9 +103,9 @@
mMappingFile = new AtomicFile(mContext.getFilesDir(), "bugreport-mapping.xml");
ArraySet<String> mAllowlistedPackages = new ArraySet<>();
mAllowlistedPackages.add(mContext.getPackageName());
- mService = new BugreportManagerServiceImpl(
- new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
- mMockUserManager, mMockDevicePolicyManager));
+ mInjector = new TestInjector(mContext, mAllowlistedPackages, mMappingFile,
+ mMockUserManager, mMockDevicePolicyManager);
+ mService = new BugreportManagerServiceImpl(mInjector);
mBugreportFileManager = new BugreportManagerServiceImpl.BugreportFileManager(mMappingFile);
when(mPackageManager.getPackageUidAsUser(anyString(), anyInt())).thenReturn(mCallingUid);
// The calling user is an admin user by default.
@@ -190,6 +197,33 @@
}
@Test
+ public void testStartBugreport() throws Exception {
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
+ public void testStartBugreport_nonAdminProfileOfAdminCurrentUser() throws Exception {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ when(mMockUserManager.isUserAdmin(callingUserId)).thenReturn(false);
+ when(mMockUserManager.getProfileParent(callingUserId)).thenReturn(ADMIN_USER_INFO);
+
+ mService.startBugreport(mCallingUid, mContext.getPackageName(),
+ new FileDescriptor(), /* screenshotFd= */ null,
+ BugreportParams.BUGREPORT_MODE_FULL,
+ /* flags= */ 0, new Listener(new CountDownLatch(1)),
+ /* isScreenshotRequested= */ false);
+
+ assertThat(mInjector.isBugreportStarted()).isTrue();
+ }
+
+ @Test
public void testStartBugreport_throwsForNonAdminUser() throws Exception {
when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
@@ -317,8 +351,12 @@
private static class TestInjector extends BugreportManagerServiceImpl.Injector {
+ private static final String SYSTEM_PROPERTY_BUGREPORT_START = "ctl.start";
+ private static final String SYSTEM_PROPERTY_BUGREPORT_STOP = "ctl.stop";
+
private final UserManager mUserManager;
private final DevicePolicyManager mDevicePolicyManager;
+ private boolean mBugreportStarted = false;
TestInjector(Context context, ArraySet<String> allowlistedPackages, AtomicFile mappingFile,
UserManager um, DevicePolicyManager dpm) {
@@ -336,5 +374,20 @@
public DevicePolicyManager getDevicePolicyManager() {
return mDevicePolicyManager;
}
+
+ @Override
+ public void setSystemProperty(String key, String value) {
+ // Calling SystemProperties.set() will throw a RuntimeException due to permission error.
+ // Instead, we are just marking a flag to store the state for testing.
+ if (SYSTEM_PROPERTY_BUGREPORT_START.equals(key)) {
+ mBugreportStarted = true;
+ } else if (SYSTEM_PROPERTY_BUGREPORT_STOP.equals(key)) {
+ mBugreportStarted = false;
+ }
+ }
+
+ public boolean isBugreportStarted() {
+ return mBugreportStarted;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 507b3fe..1591a96 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -316,6 +316,10 @@
.that(userTypeDetails).isNotNull();
final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference();
+ // Only run the test if private profile creation is enabled on the device
+ assumeTrue("Private profile not enabled on the device",
+ mUserManager.canAddPrivateProfile());
+
// Test that only one private profile can be created
final int mainUserId = mainUser.getIdentifier();
UserInfo userInfo = createProfileForUser("Private profile1",
@@ -1231,6 +1235,20 @@
@MediumTest
@Test
+ public void testPrivateProfileCreationRestrictions() {
+ assumeTrue(mUserManager.canAddPrivateProfile());
+ final int mainUserId = ActivityManager.getCurrentUser();
+ try {
+ UserInfo privateProfileInfo = createProfileForUser("Private",
+ UserManager.USER_TYPE_PROFILE_PRIVATE, mainUserId);
+ assertThat(privateProfileInfo).isNotNull();
+ } catch (Exception e) {
+ fail("Creation of private profile failed due to " + e.getMessage());
+ }
+ }
+
+ @MediumTest
+ @Test
public void testAddRestrictedProfile() throws Exception {
if (isAutomotive() || UserManager.isHeadlessSystemUserMode()) return;
assertWithMessage("There should be no associated restricted profiles before the test")
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 2f29d10..515898a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -48,6 +48,8 @@
"notification_flags_lib",
"platform-test-rules",
"SettingsLib",
+ "libprotobuf-java-lite",
+ "platformprotoslite",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 4dded1d..05b6c90 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -37,6 +37,7 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -885,6 +886,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -915,6 +917,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -945,6 +948,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -975,6 +979,7 @@
return true;
});
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1152,6 +1157,58 @@
}
@Test
+ public void testUpgradeAppNoPermissionNoRebind() throws Exception {
+ Context context = spy(getContext());
+ doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
+ mIpm,
+ APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ addExpectedServices(service, packages, 0);
+
+ final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
+ final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
+
+ // Both components are approved initially
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryPackages.clear();
+
+ loadXml(service);
+
+ //Component package/C1 loses bind permission
+ when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
+ (Answer<ServiceInfo>) invocation -> {
+ ComponentName invocationCn = invocation.getArgument(0);
+ if (invocationCn != null) {
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationCn.getPackageName();
+ serviceInfo.name = invocationCn.getClassName();
+ if (invocationCn.equals(unapprovedComponent)) {
+ serviceInfo.permission = "none";
+ } else {
+ serviceInfo.permission = service.getConfig().bindPermission;
+ }
+ serviceInfo.metaData = null;
+ return serviceInfo;
+ }
+ return null;
+ }
+ );
+
+ // Trigger package update
+ service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
+
+ assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
+ assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
+ }
+
+ @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 33ca5c2..a45b102 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -20,6 +20,7 @@
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
@@ -32,6 +33,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.content.ComponentName;
import android.content.Context;
@@ -47,9 +49,12 @@
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Xml;
+import android.Manifest;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.NotificationManagerService.NotificationAssistants;
@@ -59,7 +64,9 @@
import org.mockito.MockitoAnnotations;
import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -89,11 +96,15 @@
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
+ ComponentName mCn = new ComponentName("a", "b");
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.string.config_defaultAssistantAccessComponent, "a/a");
mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
when(mNm.getBinderService()).thenReturn(mINm);
mContext.ensureTestableResources();
@@ -102,8 +113,9 @@
ResolveInfo resolve = new ResolveInfo();
approved.add(resolve);
ServiceInfo info = new ServiceInfo();
- info.packageName = "a";
- info.name="a";
+ info.packageName = mCn.getPackageName();
+ info.name = mCn.getClassName();
+ info.permission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
resolve.serviceInfo = info;
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
.thenReturn(approved);
@@ -137,6 +149,51 @@
}
@Test
+ public void testWriteXml_userTurnedOffNAS() throws Exception {
+ int userId = ActivityManager.getCurrentUser();
+
+ mAssistants.loadDefaultsFromConfig(true);
+
+ mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
+ true, true);
+
+ ComponentName current = CollectionUtils.firstOrNull(
+ mAssistants.getAllowedComponents(userId));
+ assertNotNull(current);
+ mAssistants.setUserSet(userId, true);
+ mAssistants.setPackageOrComponentEnabled(current.flattenToString(), userId, true, false,
+ true);
+
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mAssistants.writeXml(serializer, true, userId);
+ serializer.endDocument();
+ serializer.flush();
+
+ //fail(baos.toString("UTF-8"));
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm));
+ mAssistants.readXml(parser, allowedManagedServicePackages, false, UserHandle.USER_ALL);
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_userDisabled() throws Exception {
String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
@@ -160,6 +217,33 @@
}
@Test
+ public void testReadXml_userDisabled_restore() throws Exception {
+ String xml = "<enabled_assistants version=\"4\" defaults=\"b/b\">"
+ + "<service_listing approved=\"\" user=\"0\" primary=\"true\""
+ + "user_changed=\"true\"/>"
+ + "</enabled_assistants>";
+
+ final TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml.toString().getBytes())), null);
+ TriPredicate<String, Integer, String> allowedManagedServicePackages =
+ mNm::canUseManagedServices;
+
+ parser.nextTag();
+ mAssistants.readXml(parser, allowedManagedServicePackages, true,
+ ActivityManager.getCurrentUser());
+
+ ArrayMap<Boolean, ArraySet<String>> approved = mAssistants.mApproved.get(0);
+
+ // approved should not be null
+ assertNotNull(approved);
+ assertEquals(new ArraySet<>(), approved.get(true));
+
+ // user set is maintained
+ assertTrue(mAssistants.mIsUserChanged.get(ActivityManager.getCurrentUser()));
+ }
+
+ @Test
public void testReadXml_upgradeUserSet() throws Exception {
String xml = "<enabled_assistants version=\"3\" defaults=\"b/b\">"
+ "<service_listing approved=\"\" user=\"0\" primary=\"true\""
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
index 3499a12..bf8cfa5c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -20,8 +20,12 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -70,10 +74,11 @@
@Before
public void setUp() throws Exception {
- mJobService = new NotificationHistoryJobService();
+ mJobService = spy(new NotificationHistoryJobService());
mJobService.attachBaseContext(mContext);
mJobService.onCreate();
mJobService.onBind(/* intent= */ null); // Create JobServiceEngine within JobService.
+ doNothing().when(mJobService).jobFinished(any(), eq(false));
mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
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 e3ea55a..03f2749 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -14092,7 +14092,8 @@
@Test
public void testProfileUnavailableIntent() throws RemoteException {
- mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
simulateProfileAvailabilityActions(Intent.ACTION_PROFILE_UNAVAILABLE);
verify(mWorkerHandler).post(any(Runnable.class));
verify(mSnoozeHelper).clearData(anyInt());
@@ -14101,7 +14102,8 @@
@Test
public void testManagedProfileUnavailableIntent() throws RemoteException {
- mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+ mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE,
+ android.multiuser.Flags.FLAG_ENABLE_PRIVATE_SPACE_FEATURES);
simulateProfileAvailabilityActions(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
verify(mWorkerHandler).post(any(Runnable.class));
verify(mSnoozeHelper).clearData(anyInt());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 78fb570..bfc47fd 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -324,7 +324,11 @@
when(mPermissionHelper.getNotificationPermissionValues(USER_SYSTEM))
.thenReturn(appPermissions);
- when(mUserProfiles.getCurrentProfileIds()).thenReturn(IntArray.wrap(new int[] {0}));
+ IntArray currentProfileIds = IntArray.wrap(new int[]{0});
+ if (UserManager.isHeadlessSystemUserMode()) {
+ currentProfileIds.add(UserHandle.getUserId(UID_HEADLESS));
+ }
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(currentProfileIds);
mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
mPermissionHelper, mPermissionManager, mLogger, mAppOpsManager, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 99d5a6d..75552bc 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -16,7 +16,7 @@
package com.android.server.notification;
-import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
new file mode 100644
index 0000000..f724510
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenEnumTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.provider.Settings;
+import android.service.notification.ZenPolicy;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.os.dnd.ActiveRuleType;
+import com.android.os.dnd.ChannelPolicy;
+import com.android.os.dnd.ConversationType;
+import com.android.os.dnd.PeopleType;
+import com.android.os.dnd.State;
+import com.android.os.dnd.ZenMode;
+
+import com.google.protobuf.Internal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/** Test to validate that logging enums used in Zen classes match their API definitions. */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenEnumTest {
+
+ @Test
+ public void testEnum_zenMode() {
+ testEnum(Settings.Global.class, "ZEN_MODE", ZenMode.class, "ZEN_MODE");
+ }
+
+ @Test
+ public void testEnum_activeRuleType() {
+ testEnum(AutomaticZenRule.class, "TYPE", ActiveRuleType.class, "TYPE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyState() {
+ testEnum(ZenPolicy.class, "STATE", State.class, "STATE");
+ }
+
+ @Test
+ public void testEnum_zenPolicyChannelPolicy() {
+ testEnum(ZenPolicy.class, "CHANNEL_POLICY", ChannelPolicy.class, "CHANNEL_POLICY");
+ }
+
+ @Test
+ public void testEnum_zenPolicyConversationType() {
+ testEnum(ZenPolicy.class, "CONVERSATION_SENDERS", ConversationType.class, "CONV");
+ }
+
+ @Test
+ public void testEnum_zenPolicyPeopleType() {
+ testEnum(ZenPolicy.class, "PEOPLE_TYPE", PeopleType.class, "PEOPLE");
+ }
+
+ /**
+ * Verifies that any constants (i.e. {@code public static final int} fields) named {@code
+ * <apiPrefix>_SOMETHING} in {@code apiClass} are present and have the same numerical value
+ * in the enum values defined in {@code loggingProtoEnumClass}.
+ *
+ * <p>Note that <em>extra</em> values in the logging enum are accepted (since we have one of
+ * those, and the main goal of this test is that we don't forget to update the logging enum
+ * if new API enum values are added).
+ */
+ private static void testEnum(Class<?> apiClass, String apiPrefix,
+ Class<? extends Internal.EnumLite> loggingProtoEnumClass,
+ String loggingPrefix) {
+ Map<String, Integer> apiConstants =
+ Arrays.stream(apiClass.getDeclaredFields())
+ .filter(f -> Modifier.isPublic(f.getModifiers()))
+ .filter(f -> Modifier.isStatic(f.getModifiers()))
+ .filter(f -> Modifier.isFinal(f.getModifiers()))
+ .filter(f -> f.getType().equals(int.class))
+ .filter(f -> f.getName().startsWith(apiPrefix + "_"))
+ .collect(Collectors.toMap(
+ Field::getName,
+ ZenEnumTest::getStaticFieldIntValue));
+
+ Map<String, Integer> loggingConstants =
+ Arrays.stream(loggingProtoEnumClass.getEnumConstants())
+ .collect(Collectors.toMap(
+ v -> v.toString(),
+ v -> v.getNumber()));
+
+ Map<String, Integer> renamedApiConstants = apiConstants.entrySet().stream()
+ .collect(Collectors.toMap(
+ entry -> entry.getKey().replace(apiPrefix + "_", loggingPrefix + "_"),
+ Map.Entry::getValue));
+
+ assertThat(loggingConstants).containsAtLeastEntriesIn(renamedApiConstants);
+ }
+
+ private static int getStaticFieldIntValue(Field f) {
+ try {
+ return f.getInt(null);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 495e01a..c4d2460 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ConfigChangeOrigin;
@@ -4807,6 +4808,53 @@
@Test
@EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleChanged_deactivatesRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateWithDiff = new AutomaticZenRule.Builder(rule)
+ .setTriggerDescription("Whenever")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateWithDiff, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isNull();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void updateAutomaticZenRule_ruleNotChanged_doesNotDeactivateRule() {
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
+ AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", CONDITION_ID)
+ .setConfigurationActivity(new ComponentName(mPkg, "cls"))
+ .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .build();
+ String ruleId = mZenModeHelper.addAutomaticZenRule(mPkg, rule, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP,
+ CUSTOM_PKG_UID);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ AutomaticZenRule updateUnchanged = new AutomaticZenRule.Builder(rule).build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, updateUnchanged, UPDATE_ORIGIN_APP, "reason",
+ CUSTOM_PKG_UID);
+
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules.get(ruleId).condition).isEqualTo(
+ CONDITION_TRUE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
public void removeAutomaticZenRule_propagatesOriginToEffectsApplier() {
mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier);
reset(mDeviceEffectsApplier);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
index 038f1db..54ab367 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/GroupedAggregatedLogRecordsTest.java
@@ -135,7 +135,7 @@
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_2, createTime++));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
@@ -155,7 +155,7 @@
AggregatedLogRecord<TestSingleLogRecord> droppedRecord =
records.add(createRecord(GROUP_1, KEY_1, createTime + AGGREGATION_TIME_LIMIT));
assertThat(droppedRecord).isNotNull();
- assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.getFirst());
+ assertThat(droppedRecord.getLatest()).isEqualTo(mTestRecords.get(0));
dumpRecords(records);
assertGroupHeadersWrittenOnce(records, GROUP_1);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index f54c7e5..88a9483 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -604,7 +604,8 @@
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOff_shouldIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_MEDIUM);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 0 /* OFF*/);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// Keyboard touch ignored.
assertVibrationIgnoredForAttributes(
@@ -628,7 +629,8 @@
@RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
public void shouldIgnoreVibration_withKeyboardSettingsOn_shouldNotIgnoreKeyboardVibration() {
setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
- setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(true);
// General touch ignored.
assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
@@ -642,6 +644,25 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED)
+ public void shouldIgnoreVibration_noFixedKeyboardAmplitude_ignoresKeyboardTouchVibration() {
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, VIBRATION_INTENSITY_OFF);
+ setUserSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, 1 /* ON */);
+ setHasFixedKeyboardAmplitudeIntensity(false);
+
+ // General touch ignored.
+ assertVibrationIgnoredForUsage(USAGE_TOUCH, Vibration.Status.IGNORED_FOR_SETTINGS);
+
+ // Keyboard touch ignored.
+ assertVibrationIgnoredForAttributes(
+ new VibrationAttributes.Builder()
+ .setUsage(USAGE_TOUCH)
+ .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
+ .build(),
+ Vibration.Status.IGNORED_FOR_SETTINGS);
+ }
+
+ @Test
public void shouldIgnoreVibrationFromVirtualDevices_defaultDevice_neverIgnored() {
// Vibrations from the primary device is never ignored.
for (int usage : ALL_USAGES) {
@@ -953,6 +974,10 @@
when(mVibrationConfigMock.ignoreVibrationsOnWirelessCharger()).thenReturn(ignore);
}
+ private void setHasFixedKeyboardAmplitudeIntensity(boolean hasFixedAmplitude) {
+ when(mVibrationConfigMock.hasFixedKeyboardAmplitude()).thenReturn(hasFixedAmplitude);
+ }
+
private void deleteUserSetting(String settingName) {
Settings.System.putStringForUser(
mContextSpy.getContentResolver(), settingName, null, UserHandle.USER_CURRENT);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 6e478d8..55fc03e 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -110,6 +110,7 @@
@Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks;
@Mock private IBinder mVibrationToken;
@Mock private VibrationConfig mVibrationConfigMock;
+ @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
private VibrationSettings mVibrationSettings;
@@ -255,6 +256,7 @@
USAGE_RINGTONE);
waitForCompletion();
+ verify(mStatsLoggerMock, never()).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
List<Float> amplitudes = mVibratorProviders.get(VIBRATOR_ID).getAmplitudes();
@@ -274,6 +276,7 @@
long vibrationId = startThreadAndDispatcher(effect, neverCompletingFuture, USAGE_RINGTONE);
waitForCompletion();
+ verify(mStatsLoggerMock).logVibrationParamRequestTimeout(UID);
assertEquals(Arrays.asList(expectedOneShot(15)),
mVibratorProviders.get(VIBRATOR_ID).getEffectSegments(vibrationId));
assertEquals(expectedAmplitudes(1, 1, 1),
@@ -1679,7 +1682,7 @@
mControllers = createVibratorControllers();
DeviceAdapter deviceAdapter = new DeviceAdapter(mVibrationSettings, mControllers);
mVibrationConductor = new VibrationStepConductor(vib, mVibrationSettings, deviceAdapter,
- mVibrationScaler, requestVibrationParamsFuture, mManagerHooks);
+ mVibrationScaler, mStatsLoggerMock, requestVibrationParamsFuture, mManagerHooks);
assertTrue(mThread.runVibrationOnVibrationThread(mVibrationConductor));
return mVibrationConductor.getVibration().id;
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 3799abc..3f5217c 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -27,6 +27,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -38,6 +42,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.test.TestLooper;
import android.util.SparseArray;
@@ -59,13 +64,14 @@
public class VibratorControlServiceTest {
+ private static final int UID = Process.ROOT_UID;
+
@Rule
public MockitoRule rule = MockitoJUnit.rule();
- @Mock
- private VibrationScaler mMockVibrationScaler;
- @Mock
- private PackageManagerInternal mPackageManagerInternalMock;
+ @Mock private VibrationScaler mMockVibrationScaler;
+ @Mock private PackageManagerInternal mPackageManagerInternalMock;
+ @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
private TestLooper mTestLooper;
private FakeVibratorController mFakeVibratorController;
@@ -88,7 +94,7 @@
mFakeVibratorController = new FakeVibratorController(mTestLooper.getLooper());
mVibratorControlService = new VibratorControlService(
InstrumentationRegistry.getContext(), new VibratorControllerHolder(),
- mMockVibrationScaler, mVibrationSettings, mLock);
+ mMockVibrationScaler, mVibrationSettings, mStatsLoggerMock, mLock);
}
@Test
@@ -123,7 +129,7 @@
mVibratorControlService.registerVibratorController(mFakeVibratorController);
int timeoutInMillis = 10;
CompletableFuture<Void> future =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
IBinder token = mVibratorControlService.getRequestVibrationParamsToken();
@@ -134,6 +140,11 @@
mVibratorControlService.onRequestVibrationParamsComplete(token,
VibrationParamGenerator.generateVibrationParams(vibrationScales));
+ verify(mStatsLoggerMock).logVibrationParamRequestLatency(eq(UID), anyLong());
+ verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -150,7 +161,7 @@
mVibratorControlService.registerVibratorController(mFakeVibratorController);
int timeoutInMillis = 10;
CompletableFuture<Void> unusedFuture =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
SparseArray<Float> vibrationScales = new SparseArray<>();
@@ -160,6 +171,9 @@
mVibratorControlService.onRequestVibrationParamsComplete(new Binder(),
VibrationParamGenerator.generateVibrationParams(vibrationScales));
+ verify(mStatsLoggerMock).logVibrationParamResponseIgnored();
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -174,6 +188,10 @@
VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.7f);
+ verify(mStatsLoggerMock).logVibrationParamScale(0.4f);
+ verifyNoMoreInteractions(mStatsLoggerMock);
+
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_ALARM, 0.7f);
verify(mMockVibrationScaler).updateAdaptiveHapticsScale(USAGE_NOTIFICATION, 0.4f);
// Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
@@ -192,6 +210,7 @@
VibrationParamGenerator.generateVibrationParams(vibrationScales),
mFakeVibratorController);
+ verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -202,6 +221,8 @@
mVibratorControlService.clearVibrationParams(types, mFakeVibratorController);
+ verify(mStatsLoggerMock).logVibrationParamScale(-1f);
+
verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_ALARM);
verify(mMockVibrationScaler).removeAdaptiveHapticsScale(USAGE_NOTIFICATION);
// Clearing ScaleParam.TYPE_NOTIFICATION will clear vibration scaling for both
@@ -214,6 +235,7 @@
mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM,
mFakeVibratorController);
+ verify(mStatsLoggerMock, never()).logVibrationParamScale(anyFloat());
verifyZeroInteractions(mMockVibrationScaler);
}
@@ -222,7 +244,7 @@
int timeoutInMillis = 10;
mVibratorControlService.registerVibratorController(mFakeVibratorController);
CompletableFuture<Void> future =
- mVibratorControlService.triggerVibrationParamsRequest(USAGE_RINGTONE,
+ mVibratorControlService.triggerVibrationParamsRequest(UID, USAGE_RINGTONE,
timeoutInMillis);
try {
future.orTimeout(timeoutInMillis, TimeUnit.MILLISECONDS).get();
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 1ea90f5..d6ac517 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -70,7 +70,6 @@
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.Process;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationAttributes;
@@ -1551,6 +1550,48 @@
}
@Test
+ @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception {
+ // Keep user settings the same as device default so only adaptive scale is applied.
+ setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_ALARM));
+ setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION));
+ setUserSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+ mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_TOUCH));
+
+ mockVibrators(1);
+ FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+ fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS);
+ fakeVibrator.setSupportedPrimitives(VibrationEffect.Composition.PRIMITIVE_CLICK);
+ VibratorManagerService service = createSystemReadyService();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(
+ VibrationParamGenerator.generateVibrationParams(vibrationScales),
+ mFakeVibratorController);
+
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .compose();
+ vibrateAndWaitUntilFinished(service, effect, ALARM_ATTRS);
+ vibrateAndWaitUntilFinished(service, effect, NOTIFICATION_ATTRS);
+ vibrateAndWaitUntilFinished(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ List<VibrationEffectSegment> segments = fakeVibrator.getAllEffectSegments();
+ assertEquals(3, segments.size());
+ assertEquals(0.7f, ((PrimitiveSegment) segments.get(0)).getScale(), 1e-5);
+ assertEquals(0.4f, ((PrimitiveSegment) segments.get(1)).getScale(), 1e-5);
+ assertEquals(1f, ((PrimitiveSegment) segments.get(2)).getScale(), 1e-5);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
+ }
+
+ @Test
public void vibrate_withPowerModeChange_cancelVibrationIfNotAllowed() throws Exception {
mockVibrators(1, 2);
VibratorManagerService service = createSystemReadyService();
@@ -1998,8 +2039,7 @@
@Test
@RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
- public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales()
- throws RemoteException {
+ public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2020,6 +2060,7 @@
mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 0.7f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.7f);
externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
AUDIO_NOTIFICATION_ATTRS,
@@ -2028,6 +2069,7 @@
mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 0.4f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 0.4f);
AudioAttributes ringtoneAudioAttrs = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -2036,14 +2078,15 @@
ringtoneAudioAttrs,
mock(IExternalVibrationController.class));
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
}
@Test
@RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
- public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone()
- throws RemoteException {
+ public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL,
IVibrator.CAP_AMPLITUDE_CONTROL);
@@ -2072,8 +2115,10 @@
AUDIO_NOTIFICATION_ATTRS,
mock(IExternalVibrationController.class));
scale = mExternalVibratorService.onExternalVibrationStart(externalVibration);
+ mExternalVibratorService.onExternalVibrationStop(externalVibration);
assertEquals(scale.adaptiveHapticsScale, 1f, 0);
+ verify(mVibratorFrameworkStatsLoggerMock).logVibrationAdaptiveHapticScale(UID, 1f);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 09e7b91..45a2ba4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3147,12 +3147,12 @@
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Although the activity is already visible, app
// transition animation should be applied on this activity. This might be unnecessary, but
// until we verify no logic relies on this behavior, we'll keep this as is.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(true);
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
@@ -3167,11 +3167,11 @@
// By default, activity is visible.
assertTrue(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be invisible. Since the visibility changes, app transition
// animation should be applied on this activity.
+ mDisplayContent.prepareAppTransition(0);
activity.setVisibility(false);
assertTrue(activity.isVisible());
assertFalse(activity.isVisibleRequested());
@@ -3187,7 +3187,6 @@
// activity.
assertFalse(activity.isVisible());
assertTrue(activity.isVisibleRequested());
- assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
// Request the activity to be visible. Since the visibility changes, app transition
@@ -3389,6 +3388,7 @@
// frozen until the input started.
mDisplayContent.setImeLayeringTarget(app1);
mDisplayContent.updateImeInputAndControlTarget(app1);
+ mDisplayContent.computeImeTarget(true /* updateImeTarget */);
performSurfacePlacementAndWaitForWindowAnimator();
assertEquals(app1, mDisplayContent.getImeInputTarget());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 527ea0d..ce90504 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1239,7 +1239,7 @@
final ActivityRecord activity1 = finishTopActivity(rootTask1);
assertEquals(DESTROYING, activity1.getState());
verify(mRootWindowContainer).ensureVisibilityAndConfig(eq(null) /* starting */,
- eq(display.mDisplayId), anyBoolean());
+ eq(display), anyBoolean());
}
private ActivityRecord finishTopActivity(Task task) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 6013063..649f520 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -554,7 +554,7 @@
mRootWindowContainer.applySleepTokens(true);
// The display orientation should be changed by the activity so there is no relaunch.
- verify(activity, never()).relaunchActivityLocked(anyBoolean());
+ verify(activity, never()).relaunchActivityLocked(anyBoolean(), anyInt());
assertEquals(rotatedConfig.orientation, display.getConfiguration().orientation);
}
@@ -835,8 +835,6 @@
new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setSystemDecorations(true).build();
- doReturn(true).when(mRootWindowContainer)
- .ensureVisibilityAndConfig(any(), anyInt(), anyBoolean());
doReturn(true).when(mRootWindowContainer).canStartHomeOnDisplayArea(any(), any(),
anyBoolean());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 01bd96b..5360a10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -896,6 +896,11 @@
assertFalse(mWm.moveFocusToTopEmbeddedWindow(winRightTop));
// The focus should NOT change.
assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
+
+ // Do not move focus if the dim is boosted.
+ taskFragmentLeft.mDimmerSurfaceBoosted = true;
+ assertFalse(mWm.moveFocusToTopEmbeddedWindow(winLeftTop));
+ assertEquals(winRightTop, mDisplayContent.mCurrentFocus);
}
}
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 8b44579..36adeec 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -380,6 +380,11 @@
return false;
}
+ if (descriptors == null) {
+ Slog.e(TAG, "Failed to add device as the descriptor is null");
+ return false;
+ }
+
UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress, descriptors);
if (deviceClass == UsbConstants.USB_CLASS_PER_INTERFACE
&& !checkUsbInterfacesDenyListed(parser)) {
@@ -462,8 +467,7 @@
}
// Tracking
- addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT,
- parser.getRawDescriptors());
+ addConnectionRecord(deviceAddress, ConnectionRecord.CONNECT, descriptors);
// Stats collection
FrameworkStatsLog.write(FrameworkStatsLog.USB_DEVICE_ATTACHED,
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2fc6a22..8b503f2 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -28,6 +28,8 @@
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telecom.RemoteServiceCallback;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
import java.util.ArrayList;
import java.util.HashMap;
@@ -550,6 +552,9 @@
private final Map<String, RemoteConference> mConferenceById = new HashMap<>();
private final Set<RemoteConnection> mPendingConnections = new HashSet<>();
+ /** Telecom feature flags **/
+ private final FeatureFlags mTelecomFeatureFlags = new FeatureFlagsImpl();
+
RemoteConnectionService(
IConnectionService outgoingConnectionServiceRpc,
ConnectionService ourConnectionServiceImpl) throws RemoteException {
@@ -578,6 +583,14 @@
extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
+ // Defaulted ConnectionRequest params
+ String telecomCallId = "";
+ boolean shouldShowIncomingUI = false;
+ if (mTelecomFeatureFlags.setRemoteConnectionCallId()) {
+ telecomCallId = id;
+ shouldShowIncomingUI = request.shouldShowIncomingCallUi();
+ }
+
final ConnectionRequest newRequest = new ConnectionRequest.Builder()
.setAccountHandle(request.getAccountHandle())
.setAddress(request.getAddress())
@@ -585,6 +598,9 @@
.setVideoState(request.getVideoState())
.setRttPipeFromInCall(request.getRttPipeFromInCall())
.setRttPipeToInCall(request.getRttPipeToInCall())
+ // Flagged changes
+ .setTelecomCallId(telecomCallId)
+ .setShouldShowIncomingCallUi(shouldShowIncomingUI)
.build();
try {
if (mConnectionById.isEmpty()) {
@@ -626,10 +642,28 @@
mOutgoingConnectionServiceRpc.addConnectionServiceAdapter(mServant.getStub(),
null /*Session.Info*/);
}
+
+ // Set telecom call id to what's being tracked by base ConnectionService.
+ String telecomCallId = mTelecomFeatureFlags.setRemoteConnectionCallId()
+ ? id : request.getTelecomCallId();
+
+ final ConnectionRequest newRequest = new ConnectionRequest.Builder()
+ .setAccountHandle(request.getAccountHandle())
+ .setAddress(request.getAddress())
+ .setExtras(request.getExtras())
+ .setVideoState(request.getVideoState())
+ .setShouldShowIncomingCallUi(request.shouldShowIncomingCallUi())
+ .setRttPipeFromInCall(request.getRttPipeFromInCall())
+ .setRttPipeToInCall(request.getRttPipeToInCall())
+ .setParticipants(request.getParticipants())
+ .setIsAdhocConferenceCall(request.isAdhocConferenceCall())
+ .setTelecomCallId(telecomCallId)
+ .build();
+
RemoteConference conference = new RemoteConference(id, mOutgoingConnectionServiceRpc);
mOutgoingConnectionServiceRpc.createConference(connectionManagerPhoneAccount,
id,
- request,
+ newRequest,
isIncoming,
false /* isUnknownCall */,
null /*Session.info*/);
@@ -640,7 +674,7 @@
maybeDisconnectAdapter();
}
});
- conference.putExtras(request.getExtras());
+ conference.putExtras(newRequest.getExtras());
return conference;
} catch (RemoteException e) {
return RemoteConference.failure(
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 9792cdd..048b1b2 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1434,11 +1434,14 @@
}
/**
- * This API will return all {@link PhoneAccount}s registered via
- * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
- * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount}
- * or the caller registered the {@link PhoneAccount} under a different user and does not
- * have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ * This API will return all {@link PhoneAccount}s the caller registered via
+ * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}. If a {@link PhoneAccount} appears
+ * to be missing from the list, Telecom has either unregistered the {@link PhoneAccount} (for
+ * cleanup purposes) or the caller registered the {@link PhoneAccount} under a different user
+ * and does not have the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission.
+ * <b>Note:</b> This API will only return {@link PhoneAccount}s registered by the same app. For
+ * system Dialers that need all the {@link PhoneAccount}s registered by every application, see
+ * {@link TelecomManager#getAllPhoneAccounts()}.
*
* @return all the {@link PhoneAccount}s registered by the caller.
*/
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 5d99acd..2150b5d 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -5307,6 +5307,19 @@
KEY_PREFIX + "enable_presence_group_subscribe_bool";
/**
+ * SIP SUBSCRIBE retry duration used when device doesn't receive a response to SIP
+ * SUBSCRIBE request.
+ * If this value is not defined or defined as negative value, the device does not retry
+ * the SIP SUBSCRIBE.
+ * If the value is 0 then device retries immediately upon timeout.
+ * If the value is > 0 then device waits for configured duration and retries after timeout
+ * is detected
+ * @hide
+ */
+ public static final String KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG =
+ KEY_PREFIX + "subscribe_retry_duration_millis_long";
+
+ /**
* Flag indicating whether or not to use SIP URI when send a presence subscribe.
* When {@code true}, the device sets the To and Contact header to be SIP URI using
* the TelephonyManager#getIsimDomain" API.
@@ -5982,6 +5995,7 @@
defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false);
defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, false);
+ defaults.putInt(KEY_SUBSCRIBE_RETRY_DURATION_MILLIS_LONG, -1);
defaults.putBoolean(KEY_USE_SIP_URI_FOR_PRESENCE_SUBSCRIBE_BOOL, false);
defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60);
defaults.putBoolean(KEY_RCS_REQUEST_FORBIDDEN_BY_SIP_489_BOOL, false);
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index b84ff29..12e04c2 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -195,7 +195,8 @@
// whether or not ImsFeature.FEATURE_EMERGENCY_MMTEL feature is set and should
// not be set by users of ImsService.
CAPABILITY_SIP_DELEGATE_CREATION,
- CAPABILITY_TERMINAL_BASED_CALL_WAITING
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING,
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING
})
@Retention(RetentionPolicy.SOURCE)
public @interface ImsServiceCapability {}
@@ -206,7 +207,9 @@
*/
private static final Map<Long, String> CAPABILITIES_LOG_MAP = Map.of(
CAPABILITY_EMERGENCY_OVER_MMTEL, "EMERGENCY_OVER_MMTEL",
- CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION");
+ CAPABILITY_SIP_DELEGATE_CREATION, "SIP_DELEGATE_CREATION",
+ CAPABILITY_TERMINAL_BASED_CALL_WAITING, "TERMINAL_BASED_CALL_WAITING",
+ CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING, "SIMULTANEOUS_CALLING");
/**
* The intent that must be defined as an intent-filter in the AndroidManifest of the ImsService.
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp
index 1d71f95..d658d59 100644
--- a/tests/FlickerTests/Android.bp
+++ b/tests/FlickerTests/Android.bp
@@ -63,17 +63,20 @@
],
}
-android_library_import {
- name: "wm-flicker-window-extensions_nodeps",
- aars: ["libs/window-extensions-release.aar"],
- sdk_version: "current",
-}
-
java_library {
name: "wm-flicker-window-extensions",
sdk_version: "current",
static_libs: [
- "wm-flicker-window-extensions_nodeps",
+ "androidx.window.extensions_extensions-nodeps",
+ ],
+ installable: false,
+}
+
+java_library {
+ name: "wm-flicker-window-extensions-core",
+ sdk_version: "current",
+ static_libs: [
+ "androidx.window.extensions.core_core-nodeps",
],
installable: false,
}
diff --git a/tests/FlickerTests/libs/window-extensions-release.aar b/tests/FlickerTests/libs/window-extensions-release.aar
deleted file mode 100644
index 918e514..0000000
--- a/tests/FlickerTests/libs/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
index 7343ba1c..e60764f 100644
--- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt
@@ -24,6 +24,7 @@
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
+import android.hardware.input.KeyboardLayoutSelectionResult
import android.hardware.input.IInputManager
import android.hardware.input.InputManager
import android.hardware.input.InputManagerGlobal
@@ -525,13 +526,13 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_UK_LAYOUT_DESCRIPTOR
)
- val keyboardLayout =
+ assertEquals(
+ "Default UI: getKeyboardLayoutForInputDevice API should always return " +
+ "KeyboardLayoutSelectionResult.FAILED",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
- assertNull(
- "Default UI: getKeyboardLayoutForInputDevice API should always return null",
- keyboardLayout
)
}
}
@@ -545,12 +546,14 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_UK_LAYOUT_DESCRIPTOR
)
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
- ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ var result =
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
+ assertEquals(
+ "New UI: getKeyboardLayoutForInputDevice API should return the set layout",
+ ENGLISH_UK_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
)
// This should replace previously set layout
@@ -558,12 +561,14 @@
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype,
ENGLISH_US_LAYOUT_DESCRIPTOR
)
- assertEquals(
- "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
- ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result =
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo, imeSubtype
)
+ assertEquals(
+ "New UI: getKeyboardLayoutForInputDevice API should return the last set layout",
+ ENGLISH_US_LAYOUT_DESCRIPTOR,
+ result.layoutDescriptor
)
}
}
@@ -734,17 +739,20 @@
createImeSubtypeForLanguageTag("ru"),
createLayoutDescriptor("keyboard_layout_russian")
)
- assertNull(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
- "layout available",
+ assertEquals(
+ "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("it")
)
)
- assertNull(
- "New UI: getDefaultKeyboardLayoutForInputDevice should return null when no " +
- "layout for script code is available",
+ assertEquals(
+ "New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTag("en-Deva")
@@ -811,8 +819,10 @@
createImeSubtypeForLanguageTagAndLayoutType("ru", ""),
createLayoutDescriptor("keyboard_layout_russian")
)
- assertNull("New UI: getDefaultKeyboardLayoutForInputDevice should return null when " +
- "no layout for script code is available",
+ assertEquals("New UI: getDefaultKeyboardLayoutForInputDevice should return " +
+ "KeyboardLayoutSelectionResult.FAILED when no layout for script code is" +
+ "available",
+ KeyboardLayoutSelectionResult.FAILED,
keyboardLayoutManager.getKeyboardLayoutForInputDevice(
keyboardDevice.identifier, USER_ID, imeInfo,
createImeSubtypeForLanguageTagAndLayoutType("en-Deva-US", "")
@@ -865,14 +875,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(keyboardDevice.vendorId),
ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
LAYOUT_TYPE_DEFAULT,
GERMAN_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"de-Latn",
- LAYOUT_TYPE_QWERTZ),
+ LAYOUT_TYPE_QWERTZ
),
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -893,13 +905,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(englishQwertyKeyboardDevice.vendorId),
ArgumentMatchers.eq(englishQwertyKeyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
"en",
LAYOUT_TYPE_QWERTY,
ENGLISH_US_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
"de-Latn",
- LAYOUT_TYPE_QWERTZ)),
+ LAYOUT_TYPE_QWERTZ
+ )
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -918,14 +933,16 @@
ArgumentMatchers.anyBoolean(),
ArgumentMatchers.eq(keyboardDevice.vendorId),
ArgumentMatchers.eq(keyboardDevice.productId),
- ArgumentMatchers.eq(createByteArray(
+ ArgumentMatchers.eq(
+ createByteArray(
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
LAYOUT_TYPE_DEFAULT,
"Default",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEFAULT,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEFAULT,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
- LAYOUT_TYPE_DEFAULT),
+ LAYOUT_TYPE_DEFAULT
),
+ ),
ArgumentMatchers.eq(keyboardDevice.deviceBus),
)
}
@@ -998,12 +1015,13 @@
imeSubtype: InputMethodSubtype,
expectedLayout: String
) {
+ val result = keyboardLayoutManager.getKeyboardLayoutForInputDevice(
+ device.identifier, USER_ID, imeInfo, imeSubtype
+ )
assertEquals(
"New UI: getDefaultKeyboardLayoutForInputDevice should return $expectedLayout",
expectedLayout,
- keyboardLayoutManager.getKeyboardLayoutForInputDevice(
- device.identifier, USER_ID, imeInfo, imeSubtype
- )
+ result.layoutDescriptor
)
}
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 89a47b9..0615941 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -17,6 +17,7 @@
package com.android.server.input
import android.hardware.input.KeyboardLayout
+import android.hardware.input.KeyboardLayoutSelectionResult
import android.icu.util.ULocale
import android.platform.test.annotations.Presubmit
import android.view.InputDevice
@@ -120,15 +121,15 @@
val event = builder.addLayoutSelection(
createImeSubtype(1, ULocale.forLanguageTag("en-US"), "qwerty"),
"English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD
).addLayoutSelection(
createImeSubtype(2, ULocale.forLanguageTag("en-US"), "azerty"),
null, // Default layout type
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER
).addLayoutSelection(
createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
).setIsFirstTimeConfiguration(true).build()
assertEquals(
@@ -158,7 +159,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"English(US)(Qwerty)",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
@@ -167,7 +168,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_USER,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
)
@@ -176,7 +177,7 @@
"de-CH",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
"en-US",
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
@@ -197,7 +198,7 @@
val event = builder.addLayoutSelection(
createImeSubtype(4, null, "qwerty"), // Default language tag
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE
).build()
assertExpectedLayoutConfiguration(
@@ -205,7 +206,7 @@
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
"German",
- KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
+ KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_DEVICE,
KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
)
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index a64996c..d9a4c26 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -59,6 +59,7 @@
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.LinkedList;
+import java.util.TreeMap;
/**
* Test class for {@link ProtoLogImpl}.
@@ -89,7 +90,7 @@
//noinspection ResultOfMethodCallIgnored
mFile.delete();
mProtoLog = new LegacyProtoLogImpl(mFile, mViewerConfigFilename,
- 1024 * 1024, mReader, 1024);
+ 1024 * 1024, mReader, 1024, new TreeMap<>());
}
@After
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 270f595..548adef 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -64,6 +64,7 @@
import java.io.IOException;
import java.util.List;
import java.util.Random;
+import java.util.TreeMap;
import perfetto.protos.Protolog;
import perfetto.protos.ProtologCommon;
@@ -152,7 +153,8 @@
.thenAnswer(it -> new ProtoInputStream(mViewerConfigBuilder.build().toByteArray()));
mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
- mProtoLog = new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader);
+ mProtoLog =
+ new PerfettoProtoLogImpl(viewerConfigInputStreamProvider, mReader, new TreeMap<>());
}
@After
diff --git a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
index 407d4bf..2977c21 100644
--- a/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
+++ b/tests/graphics/HwAccelerationTest/jni/native-lib.cpp
@@ -30,7 +30,7 @@
void setBuffer(AHardwareBuffer* buffer) {
ASurfaceTransaction* transaction = ASurfaceTransaction_create();
- ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer);
+ ASurfaceTransaction_setBuffer(transaction, surfaceControl, buffer, -1);
ASurfaceTransaction_setVisibility(transaction, surfaceControl,
ASURFACE_TRANSACTION_VISIBILITY_SHOW);
ASurfaceTransaction_apply(transaction);
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index b054a57..b4e2758 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -15,12 +15,7 @@
//
package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["Android-Apache-2.0"],
}
toolSources = [
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
deleted file mode 100644
index 15ae2ba..0000000
--- a/tools/aapt2/Android.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-include $(CLEAR_VARS)
-aapt2_results := ./out/soong/.intermediates/frameworks/base/tools/aapt2/aapt2_results
-$(call declare-1p-target,$(aapt2_results))
-aapt2_results :=
diff --git a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
index a359155..2690bc5 100644
--- a/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/CommandOptions.kt
@@ -25,6 +25,7 @@
const val READ_LOG_CMD = "read-log"
private val commands = setOf(TRANSFORM_CALLS_CMD, GENERATE_CONFIG_CMD, READ_LOG_CMD)
+ // TODO: This is always the same. I don't think it's required
private const val PROTOLOG_CLASS_PARAM = "--protolog-class"
private const val PROTOLOGGROUP_CLASS_PARAM = "--loggroups-class"
private const val PROTOLOGGROUP_JAR_PARAM = "--loggroups-jar"
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 1381847..837dae9 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -24,11 +24,17 @@
import com.github.javaparser.ParserConfiguration
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
+import com.github.javaparser.ast.NodeList
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
+import com.github.javaparser.ast.body.InitializerDeclaration
+import com.github.javaparser.ast.expr.FieldAccessExpr
import com.github.javaparser.ast.expr.MethodCallExpr
+import com.github.javaparser.ast.expr.NameExpr
import com.github.javaparser.ast.expr.NullLiteralExpr
+import com.github.javaparser.ast.expr.ObjectCreationExpr
import com.github.javaparser.ast.expr.SimpleName
import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.stmt.BlockStmt
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
@@ -39,8 +45,7 @@
import java.util.concurrent.Executors
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
-import kotlin.math.abs
-import kotlin.random.Random
+import kotlin.math.absoluteValue
import kotlin.system.exitProcess
object ProtoLogTool {
@@ -72,7 +77,11 @@
}
private fun processClasses(command: CommandOptions) {
- val generationHash = abs(Random.nextInt())
+ // A deterministic hash based on the group jar path and the source files we are processing.
+ // The hash is required to make sure different ProtoLogImpls don't conflict.
+ val generationHash = (command.javaSourceArgs.toTypedArray() + command.protoLogGroupsJarArg)
+ .contentHashCode().absoluteValue
+
// Need to generate a new impl class to inject static constants into the class.
val generatedProtoLogImplClass =
"com.android.internal.protolog.ProtoLogImpl_$generationHash"
@@ -93,7 +102,8 @@
outJar.putNextEntry(zipEntry(protologImplPath))
outJar.write(generateProtoLogImpl(protologImplName, command.viewerConfigFilePathArg,
- command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath).toByteArray())
+ command.legacyViewerConfigFilePathArg, command.legacyOutputFilePath,
+ groups, command.protoLogGroupsClassNameArg).toByteArray())
val executor = newThreadPool()
@@ -137,6 +147,8 @@
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String,
): String {
val file = File(PROTOLOG_IMPL_SRC_PATH)
@@ -157,7 +169,8 @@
classNameNode.setId(protoLogImplGenName)
injectConstants(classDeclaration,
- viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath)
+ viewerConfigFilePath, legacyViewerConfigFilePath, legacyOutputFilePath, groups,
+ protoLogGroupsClassName)
return code.toString()
}
@@ -166,7 +179,9 @@
classDeclaration: ClassOrInterfaceDeclaration,
viewerConfigFilePath: String,
legacyViewerConfigFilePath: String?,
- legacyOutputFilePath: String?
+ legacyOutputFilePath: String?,
+ groups: Map<String, LogGroup>,
+ protoLogGroupsClassName: String
) {
classDeclaration.fields.forEach { field ->
field.getAnnotationByClass(ProtoLogToolInjected::class.java)
@@ -194,6 +209,35 @@
StringLiteralExpr(it)
} ?: NullLiteralExpr())
}
+ ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
+ val initializerBlockStmt = BlockStmt()
+ for (group in groups) {
+ initializerBlockStmt.addStatement(
+ MethodCallExpr()
+ .setName("put")
+ .setArguments(
+ NodeList(StringLiteralExpr(group.key),
+ FieldAccessExpr()
+ .setScope(
+ NameExpr(
+ protoLogGroupsClassName
+ ))
+ .setName(group.value.name)))
+ )
+ group.key
+ }
+
+ val treeMapCreation = ObjectCreationExpr()
+ .setType("TreeMap<String, IProtoLogGroup>")
+ .setAnonymousClassBody(NodeList(
+ InitializerDeclaration().setBody(
+ initializerBlockStmt
+ )
+ ))
+
+ field.setFinal(true)
+ field.variables.first().setInitializer(treeMapCreation)
+ }
else -> error("Unhandled ProtoLogToolInjected value: $valueName.")
}
}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index b18bdff..b1b314fc 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -17,6 +17,7 @@
// ==========================================================
// Build the host executable: protoc-gen-javastream
// ==========================================================
+
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -41,6 +42,32 @@
static_libs: ["libprotoc"],
}
+// ==========================================================
+// Build the host static library: java_streaming_proto_lib
+// ==========================================================
+
+cc_library_host_static {
+ name: "java_streaming_proto_lib",
+ defaults: ["protoc-gen-stream-defaults"],
+ target: {
+ darwin: {
+ cflags: ["-D_DARWIN_UNLIMITED_STREAMS"],
+ },
+ },
+ cflags: [
+ "-Wno-format-y2k",
+ "-DSTATIC_ANDROIDFW_FOR_TOOLS",
+ ],
+
+ srcs: [
+ "java/java_proto_stream_code_generator.cpp",
+ ],
+}
+
+// ==========================================================
+// Build the host executable: protoc-gen-javastream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-javastream",
srcs: [
@@ -48,8 +75,13 @@
],
defaults: ["protoc-gen-stream-defaults"],
+ static_libs: ["java_streaming_proto_lib"],
}
+// ==========================================================
+// Build the host executable: protoc-gen-cppstream
+// ==========================================================
+
cc_binary_host {
name: "protoc-gen-cppstream",
srcs: [
@@ -60,13 +92,31 @@
}
// ==========================================================
+// Build the host tests: StreamingProtoTest
+// ==========================================================
+
+cc_test_host {
+ name: "StreamingProtoTest",
+ defaults: ["protoc-gen-stream-defaults"],
+ srcs: [
+ "test/unit/**/*.cpp",
+ ],
+ static_libs: [
+ "java_streaming_proto_lib",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+// ==========================================================
// Build the java test
// ==========================================================
+
java_library {
- name: "StreamingProtoTest",
+ name: "StreamingProtoJavaIntegrationTest",
srcs: [
- "test/**/*.java",
- "test/**/*.proto",
+ "test/integration/**/*.java",
+ "test/integration/**/*.proto",
],
proto: {
type: "stream",
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.cpp b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
new file mode 100644
index 0000000..9d61111
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.cpp
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "java_proto_stream_code_generator.h"
+
+#include <stdio.h>
+
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string make_outer_class_name(const FileDescriptorProto& file_descriptor) {
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name) {
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += class_name;
+ result += ".java";
+
+ return result;
+}
+
+static string indent_more(const string& indent) {
+ return indent + INDENT;
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent) {
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i = 0; i < N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int " << make_constant_name(value.name()) << " = "
+ << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Write a field.
+ */
+static void write_field(stringstream& text, const FieldDescriptorProto& field,
+ const string& indent) {
+ string optional_comment =
+ field.label() == FieldDescriptorProto::LABEL_OPTIONAL ? "optional " : "";
+ string repeated_comment =
+ field.label() == FieldDescriptorProto::LABEL_REPEATED ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed() ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void write_message(stringstream& text, const DescriptorProto& message,
+ const string& indent) {
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i = 0; i < N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i = 0; i < N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
+ */
+static void write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums,
+ const vector<DescriptorProto>& messages) {
+ stringstream text;
+
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API
+ text << "/** @hide */" << endl;
+ // text << "@android.annotation.TestApi" << endl;
+
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
+
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+
+ N = enums.size();
+ for (size_t i = 0; i < N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i = 0; i < N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void write_multiple_files(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name =
+ file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ if (messages_to_compile.empty() || !enums.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+ messages.push_back(file_descriptor.message_type(i));
+
+ if (messages_to_compile.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+ }
+}
+
+static void write_single_file(CodeGeneratorResponse* response,
+ const FileDescriptorProto& file_descriptor,
+ set<string> messages_to_compile) {
+ int N;
+
+ vector<EnumDescriptorProto> enums;
+ N = file_descriptor.enum_type_size();
+ for (int i = 0; i < N; i++) {
+ auto enum_full_name = file_descriptor.package() + "." + file_descriptor.enum_type(i).name();
+ if (!messages_to_compile.empty() && !messages_to_compile.count(enum_full_name)) {
+ continue;
+ }
+
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+ N = file_descriptor.message_type_size();
+ for (int i = 0; i < N; i++) {
+ auto message_full_name =
+ file_descriptor.package() + "." + file_descriptor.message_type(i).name();
+
+ if (!messages_to_compile.empty() && !messages_to_compile.count(message_full_name)) {
+ continue;
+ }
+
+ messages.push_back(file_descriptor.message_type(i));
+ }
+
+ if (messages_to_compile.empty() || !enums.empty() || !messages.empty()) {
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)), true,
+ enums, messages);
+ }
+}
+
+static void parse_args_string(stringstream args_string_stream,
+ set<string>* messages_to_compile_out) {
+ string line;
+ while (getline(args_string_stream, line, ';')) {
+ stringstream line_ss(line);
+ string arg_name;
+ getline(line_ss, arg_name, ':');
+ if (arg_name == "include_filter") {
+ string full_message_name;
+ while (getline(line_ss, full_message_name, ',')) {
+ messages_to_compile_out->insert(full_message_name);
+ }
+ } else {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE, "Unexpected argument '%s'.", arg_name.c_str());
+ }
+ }
+}
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request) {
+ CodeGeneratorResponse response;
+
+ set<string> messages_to_compile;
+ auto request_params = request.parameter();
+ if (!request_params.empty()) {
+ parse_args_string(stringstream(request_params), &messages_to_compile);
+ }
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i = 0; i < N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor, messages_to_compile);
+ } else {
+ write_single_file(&response, file_descriptor, messages_to_compile);
+ }
+ }
+ }
+
+ return response;
+}
diff --git a/tools/streaming_proto/java/java_proto_stream_code_generator.h b/tools/streaming_proto/java/java_proto_stream_code_generator.h
new file mode 100644
index 0000000..d2492f7
--- /dev/null
+++ b/tools/streaming_proto/java/java_proto_stream_code_generator.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+#define AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
+
+#include "stream_proto_utils.h"
+#include "string_utils.h"
+
+using namespace android::stream_proto;
+using namespace google::protobuf::io;
+using namespace std;
+
+CodeGeneratorResponse generate_java_protostream_code(CodeGeneratorRequest request);
+
+#endif // AOSP_MAIN_FRAMEWORKS_BASE_JAVAPROTOSTREAMCODEGENERATOR_H
\ No newline at end of file
diff --git a/tools/streaming_proto/java/main.cpp b/tools/streaming_proto/java/main.cpp
index c9c50a5..5b35504 100644
--- a/tools/streaming_proto/java/main.cpp
+++ b/tools/streaming_proto/java/main.cpp
@@ -1,268 +1,21 @@
-#include "Errors.h"
-#include "stream_proto_utils.h"
-#include "string_utils.h"
-
#include <stdio.h>
+
#include <iomanip>
#include <iostream>
-#include <sstream>
#include <map>
+#include <sstream>
+#include <string>
+
+#include "Errors.h"
+#include "java_proto_stream_code_generator.h"
+#include "stream_proto_utils.h"
using namespace android::stream_proto;
using namespace google::protobuf::io;
using namespace std;
/**
- * If the descriptor gives us a class name, use that. Otherwise make one up from
- * the filename of the .proto file.
- */
-static string
-make_outer_class_name(const FileDescriptorProto& file_descriptor)
-{
- string name = file_descriptor.options().java_outer_classname();
- if (name.size() == 0) {
- name = to_camel_case(file_base_name(file_descriptor.name()));
- if (name.size() == 0) {
- ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
- "Unable to make an outer class name for file: %s",
- file_descriptor.name().c_str());
- name = "Unknown";
- }
- }
- return name;
-}
-
-/**
- * Figure out the package name that we are generating.
- */
-static string
-make_java_package(const FileDescriptorProto& file_descriptor) {
- if (file_descriptor.options().has_java_package()) {
- return file_descriptor.options().java_package();
- } else {
- return file_descriptor.package();
- }
-}
-
-/**
- * Figure out the name of the file we are generating.
- */
-static string
-make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
-{
- string const package = make_java_package(file_descriptor);
- string result;
- if (package.size() > 0) {
- result = replace_string(package, '.', '/');
- result += '/';
- }
-
- result += class_name;
- result += ".java";
-
- return result;
-}
-
-static string
-indent_more(const string& indent)
-{
- return indent + INDENT;
-}
-
-/**
- * Write the constants for an enum.
- */
-static void
-write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
-{
- const int N = enu.value_size();
- text << indent << "// enum " << enu.name() << endl;
- for (int i=0; i<N; i++) {
- const EnumValueDescriptorProto& value = enu.value(i);
- text << indent << "public static final int "
- << make_constant_name(value.name())
- << " = " << value.number() << ";" << endl;
- }
- text << endl;
-}
-
-/**
- * Write a field.
- */
-static void
-write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
-{
- string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
- ? "optional " : "";
- string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
- ? "repeated " : "";
- string proto_type = get_proto_type(field);
- string packed_comment = field.options().packed()
- ? " [packed=true]" : "";
- text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
- << field.name() << " = " << field.number() << packed_comment << ';' << endl;
-
- text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
-
- ios::fmtflags fmt(text.flags());
- text << setfill('0') << setw(16) << hex << get_field_id(field);
- text.flags(fmt);
-
- text << "L;" << endl;
-
- text << endl;
-}
-
-/**
- * Write a Message constants class.
- */
-static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent)
-{
- int N;
- const string indented = indent_more(indent);
-
- text << indent << "// message " << message.name() << endl;
- text << indent << "public final class " << message.name() << " {" << endl;
- text << endl;
-
- // Enums
- N = message.enum_type_size();
- for (int i=0; i<N; i++) {
- write_enum(text, message.enum_type(i), indented);
- }
-
- // Nested classes
- N = message.nested_type_size();
- for (int i=0; i<N; i++) {
- write_message(text, message.nested_type(i), indented);
- }
-
- // Fields
- N = message.field_size();
- for (int i=0; i<N; i++) {
- write_field(text, message.field(i), indented);
- }
-
- text << indent << "}" << endl;
- text << endl;
-}
-
-/**
- * Write the contents of a file.
*
- * If there are enums and generate_outer is false, invalid java code will be generated.
- */
-static void
-write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
- const string& filename, bool generate_outer,
- const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
-{
- stringstream text;
-
- string const package_name = make_java_package(file_descriptor);
- string const outer_class_name = make_outer_class_name(file_descriptor);
-
- text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
- text << "// source: " << file_descriptor.name() << endl << endl;
-
- if (package_name.size() > 0) {
- if (package_name.size() > 0) {
- text << "package " << package_name << ";" << endl;
- text << endl;
- }
- }
-
- // This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API
- text << "/** @hide */" << endl;
-// text << "@android.annotation.TestApi" << endl;
-
- if (generate_outer) {
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
- }
-
- size_t N;
- const string indented = generate_outer ? indent_more("") : string();
-
- N = enums.size();
- for (size_t i=0; i<N; i++) {
- write_enum(text, enums[i], indented);
- }
-
- N = messages.size();
- for (size_t i=0; i<N; i++) {
- write_message(text, messages[i], indented);
- }
-
- if (generate_outer) {
- text << "}" << endl;
- }
-
- CodeGeneratorResponse::File* file_response = response->add_file();
- file_response->set_name(filename);
- file_response->set_content(text.str());
-}
-
-/**
- * Write one file per class. Put all of the enums into the "outer" class.
- */
-static void
-write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- // If there is anything to put in the outer class file, create one
- if (file_descriptor.enum_type_size() > 0) {
- vector<EnumDescriptorProto> enums;
- int N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
- }
-
- // For each of the message types, make a file
- int N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- vector<EnumDescriptorProto> enums;
-
- vector<DescriptorProto> messages;
- messages.push_back(file_descriptor.message_type(i));
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
- false, enums, messages);
- }
-}
-
-static void
-write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
-{
- int N;
-
- vector<EnumDescriptorProto> enums;
- N = file_descriptor.enum_type_size();
- for (int i=0; i<N; i++) {
- enums.push_back(file_descriptor.enum_type(i));
- }
-
- vector<DescriptorProto> messages;
- N = file_descriptor.message_type_size();
- for (int i=0; i<N; i++) {
- messages.push_back(file_descriptor.message_type(i));
- }
-
- write_file(response, file_descriptor,
- make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
- true, enums, messages);
-}
-
-/**
* Main.
*/
int
@@ -273,24 +26,11 @@
GOOGLE_PROTOBUF_VERIFY_VERSION;
- CodeGeneratorRequest request;
- CodeGeneratorResponse response;
-
// Read the request
+ CodeGeneratorRequest request;
request.ParseFromIstream(&cin);
- // Build the files we need.
- const int N = request.proto_file_size();
- for (int i=0; i<N; i++) {
- const FileDescriptorProto& file_descriptor = request.proto_file(i);
- if (should_generate_for_file(request, file_descriptor.name())) {
- if (file_descriptor.options().java_multiple_files()) {
- write_multiple_files(&response, file_descriptor);
- } else {
- write_single_file(&response, file_descriptor);
- }
- }
- }
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
// If we had errors, don't write the response. Print the errors and exit.
if (ERRORS.HasErrors()) {
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/integration/imported.proto
similarity index 100%
rename from tools/streaming_proto/test/imported.proto
rename to tools/streaming_proto/test/integration/imported.proto
diff --git a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
similarity index 66%
copy from core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
copy to tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
index 838e41e..2a7001b 100644
--- a/core/java/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ b/tools/streaming_proto/test/integration/src/com/android/streaming_proto_test/Main.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
+/*
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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,
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-package android.hardware;
+package com.android.streaming_proto_test;
-/** @hide */
-parcelable CameraPrivacyAllowlistEntry {
- String packageName;
- boolean isMandatory;
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
}
-
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/integration/test.proto
similarity index 97%
rename from tools/streaming_proto/test/test.proto
rename to tools/streaming_proto/test/integration/test.proto
index de80ed6..3cf81b4 100644
--- a/tools/streaming_proto/test/test.proto
+++ b/tools/streaming_proto/test/integration/test.proto
@@ -16,7 +16,7 @@
syntax = "proto2";
-import "frameworks/base/tools/streaming_proto/test/imported.proto";
+import "frameworks/base/tools/streaming_proto/test/integration/imported.proto";
package com.android.streaming_proto_test;
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
deleted file mode 100644
index 1246f53..0000000
--- a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.android.streaming_proto_test;
-
-public class Main {
- public void main(String[] argv) {
- System.out.println("hello world");
- }
-}
diff --git a/tools/streaming_proto/test/unit/streaming_proto_java.cpp b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
new file mode 100644
index 0000000..8df9716
--- /dev/null
+++ b/tools/streaming_proto/test/unit/streaming_proto_java.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "java/java_proto_stream_code_generator.h"
+
+using ::testing::HasSubstr;
+using ::testing::Not;
+
+static void add_my_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+}
+
+static void add_my_other_test_proto_file(CodeGeneratorRequest* request) {
+ request->add_file_to_generate("MyOtherTestProtoFile");
+
+ FileDescriptorProto* file_desc = request->add_proto_file();
+ file_desc->set_name("MyOtherTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(false);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+}
+
+static CodeGeneratorRequest create_simple_two_file_request() {
+ CodeGeneratorRequest request;
+
+ add_my_test_proto_file(&request);
+ add_my_other_test_proto_file(&request);
+
+ return request;
+}
+
+static CodeGeneratorRequest create_simple_multi_file_request() {
+ CodeGeneratorRequest request;
+
+ request.add_file_to_generate("MyMultiMessageTestProtoFile");
+
+ FileDescriptorProto* file_desc = request.add_proto_file();
+ file_desc->set_name("MyMultiMessageTestProtoFile");
+ file_desc->set_package("test.package");
+
+ auto* file_options = file_desc->mutable_options();
+ file_options->set_java_multiple_files(true);
+
+ auto* message = file_desc->add_message_type();
+ message->set_name("MyTestMessage");
+
+ auto* field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("my_other_test_message");
+
+ message = file_desc->add_message_type();
+ message->set_name("MyOtherTestMessage");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("a_test_field");
+
+ field = message->add_field();
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_name("another_test_field");
+
+ return request;
+}
+
+TEST(StreamingProtoJavaTest, NoFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestProtoFile.java");
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestProtoFile"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter) {
+ CodeGeneratorRequest request = create_simple_two_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestProtoFile.java");
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestProtoFile"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithoutFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 2);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+
+ EXPECT_EQ(response.file(1).name(), "test/package/MyOtherTestMessage.java");
+ EXPECT_THAT(response.file(1).content(), Not(HasSubstr("class MyOtherTestProtoFile")));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("class MyOtherTestMessage"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long A_TEST_FIELD"));
+ EXPECT_THAT(response.file(1).content(), HasSubstr("long ANOTHER_TEST_FIELD"));
+}
+
+TEST(StreamingProtoJavaTest, WithFilter_MultipleJavaFiles) {
+ CodeGeneratorRequest request = create_simple_multi_file_request();
+ request.set_parameter("include_filter:test.package.MyTestMessage");
+ CodeGeneratorResponse response = generate_java_protostream_code(request);
+
+ auto generated_file_count = response.file_size();
+ EXPECT_EQ(generated_file_count, 1);
+
+ EXPECT_EQ(response.file(0).name(), "test/package/MyTestMessage.java");
+ EXPECT_THAT(response.file(0).content(), Not(HasSubstr("class MyTestProtoFile")));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("class MyTestMessage"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_TEST_FIELD"));
+ EXPECT_THAT(response.file(0).content(), HasSubstr("long MY_OTHER_TEST_FIELD"));
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 58638e8..45ab986 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -718,6 +718,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IClientInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForClientMode NullPointerException");
+ return false;
}
if (clientInterface == null) {
@@ -785,6 +788,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown client interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownClientInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown client interface");
@@ -816,6 +822,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to get IApInterface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "setupInterfaceForSoftApMode NullPointerException");
+ return false;
}
if (apInterface == null) {
@@ -854,6 +863,9 @@
} catch (RemoteException e1) {
Log.e(TAG, "Failed to teardown AP interface due to remote exception");
return false;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "tearDownSoftApInterface NullPointerException");
+ return false;
}
if (!success) {
Log.e(TAG, "Failed to teardown AP interface");
@@ -1328,6 +1340,8 @@
}
} catch (RemoteException e1) {
Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getChannelsMhzForBand NullPointerException");
}
if (result == null) {
result = new int[0];
@@ -1352,7 +1366,8 @@
*/
@Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
if (mWificond == null) {
- Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! Did wificond die?");
+ Log.e(TAG, "getDeviceWiphyCapabilities: mWificond binder is null! "
+ + "Did wificond die?");
return null;
}
@@ -1360,6 +1375,9 @@
return mWificond.getDeviceWiphyCapabilities(ifaceName);
} catch (RemoteException e) {
return null;
+ } catch (NullPointerException e2) {
+ Log.e(TAG, "getDeviceWiphyCapabilities NullPointerException");
+ return null;
}
}
@@ -1409,6 +1427,8 @@
Log.i(TAG, "Receive country code change to " + newCountryCode);
} catch (RemoteException re) {
re.rethrowFromSystemServer();
+ } catch (NullPointerException e) {
+ new RemoteException("Wificond service doesn't exist!").rethrowFromSystemServer();
}
}