Merge "Import translations. DO NOT MERGE ANYWHERE"
diff --git a/Android.bp b/Android.bp
index 4e7eba2..3d25bc1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -100,7 +100,7 @@
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
":android.hardware.keymaster-V4-java-source",
- ":android.hardware.security.keymint-V2-java-source",
+ ":android.hardware.security.keymint-V3-java-source",
":android.hardware.security.secureclock-V1-java-source",
":android.hardware.tv.tuner-V1-java-source",
":android.security.apc-java-source",
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index e3bd5ac..dcc6aa6 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -29,6 +29,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AlarmManager;
+import android.app.BroadcastOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -314,7 +315,9 @@
private Sensor mMotionSensor;
private LocationRequest mLocationRequest;
private Intent mIdleIntent;
+ private Bundle mIdleIntentOptions;
private Intent mLightIdleIntent;
+ private Bundle mLightIdleIntentOptions;
private AnyMotionDetector mAnyMotionDetector;
private final AppStateTrackerImpl mAppStateTracker;
@GuardedBy("this")
@@ -1798,10 +1801,12 @@
} catch (RemoteException e) {
}
if (deepChanged) {
- getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+ null /* receiverPermission */, mIdleIntentOptions);
}
if (lightChanged) {
- getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+ null /* receiverPermission */, mLightIdleIntentOptions);
}
EventLogTags.writeDeviceIdleOnComplete();
mGoingIdleWakeLock.release();
@@ -1821,13 +1826,13 @@
incActiveIdleOps();
mLocalActivityManager.broadcastIntentWithCallback(mIdleIntent,
mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
- null, null, null);
+ null, null, mIdleIntentOptions);
}
if (lightChanged) {
incActiveIdleOps();
mLocalActivityManager.broadcastIntentWithCallback(mLightIdleIntent,
mIdleStartedDoneReceiver, null, UserHandle.USER_ALL,
- null, null, null);
+ null, null, mLightIdleIntentOptions);
}
// Always start with one active op for the message being sent here.
// Now we are done!
@@ -1849,10 +1854,12 @@
} catch (RemoteException e) {
}
if (deepChanged) {
- getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL,
+ null /* receiverPermission */, mIdleIntentOptions);
}
if (lightChanged) {
- getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL,
+ null /* receiverPermission */, mLightIdleIntentOptions);
}
EventLogTags.writeDeviceIdleOffComplete();
} break;
@@ -2531,6 +2538,9 @@
mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+ mIdleIntentOptions = mLightIdleIntentOptions = options.toBundle();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
diff --git a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
index 5e189f2..7b38bd1 100644
--- a/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
+++ b/cmds/idmap2/include/idmap2/BinaryStreamVisitor.h
@@ -39,7 +39,7 @@
void Write8(uint8_t value);
void Write16(uint16_t value);
void Write32(uint32_t value);
- void WriteString(const StringPiece& value);
+ void WriteString(StringPiece value);
std::ostream& stream_;
};
diff --git a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
index 4b271a1..8976924 100644
--- a/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
+++ b/cmds/idmap2/libidmap2/BinaryStreamVisitor.cpp
@@ -38,7 +38,7 @@
stream_.write(reinterpret_cast<char*>(&x), sizeof(uint32_t));
}
-void BinaryStreamVisitor::WriteString(const StringPiece& value) {
+void BinaryStreamVisitor::WriteString(StringPiece value) {
// pad with null to nearest word boundary;
size_t padding_size = CalculatePadding(value.size());
Write32(value.size());
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index d517e29..dd5be21c 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -101,10 +101,10 @@
}
Result<FabricatedOverlay> FabricatedOverlay::Builder::Build() {
- using ConfigMap = std::map<std::string, TargetValue>;
- using EntryMap = std::map<std::string, ConfigMap>;
- using TypeMap = std::map<std::string, EntryMap>;
- using PackageMap = std::map<std::string, TypeMap>;
+ using ConfigMap = std::map<std::string, TargetValue, std::less<>>;
+ using EntryMap = std::map<std::string, ConfigMap, std::less<>>;
+ using TypeMap = std::map<std::string, EntryMap, std::less<>>;
+ using PackageMap = std::map<std::string, TypeMap, std::less<>>;
PackageMap package_map;
android::StringPool string_pool;
for (const auto& res_entry : entries_) {
@@ -116,8 +116,7 @@
return Error("failed to parse resource name '%s'", res_entry.resource_name.c_str());
}
- std::string package_name =
- package_substr.empty() ? target_package_name_ : package_substr.to_string();
+ std::string_view package_name = package_substr.empty() ? target_package_name_ : package_substr;
if (type_name.empty()) {
return Error("resource name '%s' missing type name", res_entry.resource_name.c_str());
}
@@ -133,17 +132,14 @@
.first;
}
- auto type = package->second.find(type_name.to_string());
+ auto type = package->second.find(type_name);
if (type == package->second.end()) {
- type =
- package->second
- .insert(std::make_pair(type_name.to_string(), EntryMap()))
- .first;
+ type = package->second.insert(std::make_pair(type_name, EntryMap())).first;
}
- auto entry = type->second.find(entry_name.to_string());
+ auto entry = type->second.find(entry_name);
if (entry == type->second.end()) {
- entry = type->second.insert(std::make_pair(entry_name.to_string(), ConfigMap())).first;
+ entry = type->second.insert(std::make_pair(entry_name, ConfigMap())).first;
}
auto value = entry->second.find(res_entry.configuration);
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 813dff1..7c0b937 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -317,7 +317,7 @@
}
std::unique_ptr<IdmapData> data(new IdmapData());
- data->string_pool_data_ = resource_mapping.GetStringPoolData().to_string();
+ data->string_pool_data_ = std::string(resource_mapping.GetStringPoolData());
uint32_t inline_value_count = 0;
std::set<std::string> config_set;
for (const auto& mapping : resource_mapping.GetTargetToOverlayMap()) {
diff --git a/cmds/idmap2/libidmap2/PolicyUtils.cpp b/cmds/idmap2/libidmap2/PolicyUtils.cpp
index 4e3f54d2..76c70ca 100644
--- a/cmds/idmap2/libidmap2/PolicyUtils.cpp
+++ b/cmds/idmap2/libidmap2/PolicyUtils.cpp
@@ -53,7 +53,7 @@
for (const auto& policy : kPolicyStringToFlag) {
if ((bitmask & policy.second) != 0) {
- policies.emplace_back(policy.first.to_string());
+ policies.emplace_back(policy.first);
}
}
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index bb31c11..b2300ce 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -89,7 +89,7 @@
// If the overlay supplies a target overlayable name, the resource must belong to the
// overlayable defined with the specified name to be overlaid.
return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
- overlay_info.target_name.c_str(), (*overlayable_info)->name.c_str());
+ overlay_info.target_name.c_str(), (*overlayable_info)->name.data());
}
// Enforce policy restrictions if the resource is declared as overlayable.
diff --git a/cmds/svc/src/com/android/commands/svc/UsbCommand.java b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
index 7d80493..26e20f6 100644
--- a/cmds/svc/src/com/android/commands/svc/UsbCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/UsbCommand.java
@@ -29,12 +29,18 @@
import java.util.function.Consumer;
import java.util.concurrent.Executor;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
public class UsbCommand extends Svc.Command {
public UsbCommand() {
super("usb");
}
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
@Override
public String shortHelp() {
return "Control Usb state";
@@ -92,8 +98,10 @@
if ("setFunctions".equals(args[1])) {
try {
+ int operationId = sUsbOperationCount.incrementAndGet();
+ System.out.println("setCurrentFunctions opId:" + operationId);
usbMgr.setCurrentFunctions(UsbManager.usbFunctionsFromString(
- args.length >= 3 ? args[2] : ""));
+ args.length >= 3 ? args[2] : ""), operationId);
} catch (RemoteException e) {
System.err.println("Error communicating with UsbManager: " + e);
}
diff --git a/core/api/current.txt b/core/api/current.txt
index aa97849..f550952 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1041,6 +1041,7 @@
field public static final int max = 16843062; // 0x1010136
field public static final int maxAspectRatio = 16844128; // 0x1010560
field public static final int maxButtonHeight = 16844029; // 0x10104fd
+ field public static final int maxConcurrentSessionsCount;
field public static final int maxDate = 16843584; // 0x1010340
field public static final int maxEms = 16843095; // 0x1010157
field public static final int maxHeight = 16843040; // 0x1010120
@@ -6968,6 +6969,7 @@
method @Deprecated public void onStart(android.content.Intent, int);
method public int onStartCommand(android.content.Intent, int, int);
method public void onTaskRemoved(android.content.Intent);
+ method public void onTimeout(int);
method public void onTrimMemory(int);
method public boolean onUnbind(android.content.Intent);
method public final void startForeground(int, android.app.Notification);
@@ -11663,6 +11665,7 @@
public class PackageInstaller {
method public void abandonSession(int);
+ method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -11707,6 +11710,35 @@
field public static final int STATUS_SUCCESS = 0; // 0x0
}
+ public static final class PackageInstaller.InstallConstraints implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isRequireAppNotForeground();
+ method public boolean isRequireAppNotInteracting();
+ method public boolean isRequireAppNotTopVisible();
+ method public boolean isRequireDeviceIdle();
+ method public boolean isRequireNotInCall();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraints> CREATOR;
+ field @NonNull public static final android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE;
+ }
+
+ public static final class PackageInstaller.InstallConstraints.Builder {
+ ctor public PackageInstaller.InstallConstraints.Builder();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints build();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle();
+ method @NonNull public android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall();
+ }
+
+ public static final class PackageInstaller.InstallConstraintsResult implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isAllConstraintsSatisfied();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.InstallConstraintsResult> CREATOR;
+ }
+
public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.graphics.Bitmap getIcon();
@@ -12444,6 +12476,7 @@
field @Deprecated public static final int FOREGROUND_SERVICE_TYPE_NONE = 0; // 0x0
field @RequiresPermission(allOf={android.Manifest.permission.FOREGROUND_SERVICE_PHONE_CALL}, anyOf={android.Manifest.permission.MANAGE_OWN_CALLS}, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4; // 0x4
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_REMOTE_MESSAGING, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512; // 0x200
+ field public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048; // 0x800
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SPECIAL_USE, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824; // 0x40000000
field @RequiresPermission(value=android.Manifest.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED, conditional=true) public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024; // 0x400
field public int flags;
@@ -12994,10 +13027,12 @@
}
public final class CreateCredentialRequest implements android.os.Parcelable {
- ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle);
+ ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
- method @NonNull public android.os.Bundle getData();
+ method @NonNull public android.os.Bundle getCandidateQueryData();
+ method @NonNull public android.os.Bundle getCredentialData();
method @NonNull public String getType();
+ method public boolean requireSystemProvider();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.CreateCredentialRequest> CREATOR;
}
@@ -13035,10 +13070,11 @@
}
public final class GetCredentialOption implements android.os.Parcelable {
- ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+ ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
method public int describeContents();
method @NonNull public android.os.Bundle getData();
method @NonNull public String getType();
+ method public boolean requireSystemProvider();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.credentials.GetCredentialOption> CREATOR;
}
@@ -15450,7 +15486,6 @@
}
public static class PathIterator.Segment {
- ctor public PathIterator.Segment(@NonNull int, @NonNull float[], float);
method public float getConicWeight();
method @NonNull public float[] getPoints();
method @NonNull public int getVerb();
@@ -32667,7 +32702,6 @@
method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.QUERY_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.content.pm.UserProperties getUserProperties(@NonNull android.os.UserHandle);
method public android.os.Bundle getUserRestrictions();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
- method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
method public static boolean isHeadlessSystemUserMode();
@@ -32681,7 +32715,6 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserRunningOrStopping(android.os.UserHandle);
method public boolean isUserUnlocked();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public boolean isUserUnlocked(android.os.UserHandle);
- method public boolean isUserVisible();
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.MODIFY_QUIET_MODE"}, conditional=true) public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle);
method public boolean requestQuietModeEnabled(boolean, @NonNull android.os.UserHandle, int);
method @Deprecated public boolean setRestrictionsChallenge(String);
@@ -48662,6 +48695,7 @@
method public float getRefreshRate();
method public int getRotation();
method @Nullable public android.view.RoundedCorner getRoundedCorner(int);
+ method @NonNull public android.view.DisplayShape getShape();
method @Deprecated public void getSize(android.graphics.Point);
method public int getState();
method public android.view.Display.Mode[] getSupportedModes();
@@ -48742,6 +48776,13 @@
method @NonNull public android.view.DisplayCutout.Builder setWaterfallInsets(@NonNull android.graphics.Insets);
}
+ public final class DisplayShape implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.graphics.Path getPath();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.view.DisplayShape> CREATOR;
+ }
+
public final class DragAndDropPermissions implements android.os.Parcelable {
method public int describeContents();
method public void release();
@@ -52123,6 +52164,7 @@
method @Deprecated @NonNull public android.view.WindowInsets consumeStableInsets();
method @Deprecated @NonNull public android.view.WindowInsets consumeSystemWindowInsets();
method @Nullable public android.view.DisplayCutout getDisplayCutout();
+ method @Nullable public android.view.DisplayShape getDisplayShape();
method @NonNull public android.graphics.Insets getInsets(int);
method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int);
method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets();
@@ -52158,6 +52200,7 @@
ctor public WindowInsets.Builder(@NonNull android.view.WindowInsets);
method @NonNull public android.view.WindowInsets build();
method @NonNull public android.view.WindowInsets.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
+ method @NonNull public android.view.WindowInsets.Builder setDisplayShape(@NonNull android.view.DisplayShape);
method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets);
method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException;
method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets);
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index b6e2d2a..3228ce6 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -121,6 +121,7 @@
field public static final int GADGET_HAL_V1_0 = 10; // 0xa
field public static final int GADGET_HAL_V1_1 = 11; // 0xb
field public static final int GADGET_HAL_V1_2 = 12; // 0xc
+ field public static final int GADGET_HAL_V2_0 = 20; // 0x14
field public static final int USB_DATA_TRANSFER_RATE_10G = 10240; // 0x2800
field public static final int USB_DATA_TRANSFER_RATE_20G = 20480; // 0x5000
field public static final int USB_DATA_TRANSFER_RATE_40G = 40960; // 0xa000
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 057c1ada..13914c5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2964,6 +2964,7 @@
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
+ method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2981,6 +2982,7 @@
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
+ method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2996,12 +2998,13 @@
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
- method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+ method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
@@ -3054,6 +3057,50 @@
}
+package android.companion.virtual.sensor {
+
+ public class VirtualSensor {
+ method @NonNull public String getName();
+ method public int getType();
+ method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+ }
+
+ public static interface VirtualSensor.SensorStateChangeCallback {
+ method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
+ }
+
+ public final class VirtualSensorConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public String getName();
+ method public int getType();
+ method @Nullable public String getVendor();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
+ }
+
+ public static final class VirtualSensorConfig.Builder {
+ ctor public VirtualSensorConfig.Builder(int, @NonNull String);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
+ }
+
+ public final class VirtualSensorEvent implements android.os.Parcelable {
+ method public int describeContents();
+ method public long getTimestampNanos();
+ method @NonNull public float[] getValues();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
+ }
+
+ public static final class VirtualSensorEvent.Builder {
+ ctor public VirtualSensorEvent.Builder(@NonNull float[]);
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
+ method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
+ }
+
+}
+
package android.content {
public class ApexEnvironment {
@@ -10049,6 +10096,8 @@
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.os.NewUserResponse createUser(@NonNull android.os.NewUserRequest);
method @NonNull public java.util.List<android.os.UserHandle> getAllProfiles();
method @NonNull public java.util.List<android.os.UserHandle> getEnabledProfiles();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getMainUser();
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public android.os.UserHandle getPreviousForegroundUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getProfileParent(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableProfileCount(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public int getRemainingCreatableUserCount(@NonNull String);
@@ -10062,6 +10111,7 @@
method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+ method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isAdminUser();
@@ -10079,6 +10129,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public boolean isUserNameSet();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isUserOfType(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean isUserUnlockingOrUnlocked(@NonNull android.os.UserHandle);
+ method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public boolean isUserVisible();
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean removeUser(@NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public int removeUserWhenPossible(@NonNull android.os.UserHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setUserIcon(@NonNull android.graphics.Bitmap) throws android.os.UserManager.UserOperationException;
@@ -12825,14 +12876,14 @@
method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
}
- public final class CallAttributes implements android.os.Parcelable {
- ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
- method public int describeContents();
- method @NonNull public android.telephony.CallQuality getCallQuality();
- method public int getNetworkType();
- method @NonNull public android.telephony.PreciseCallState getPreciseCallState();
- method public void writeToParcel(android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
+ @Deprecated public final class CallAttributes implements android.os.Parcelable {
+ ctor @Deprecated public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
+ method @Deprecated public int describeContents();
+ method @Deprecated @NonNull public android.telephony.CallQuality getCallQuality();
+ method @Deprecated public int getNetworkType();
+ method @Deprecated @NonNull public android.telephony.PreciseCallState getPreciseCallState();
+ method @Deprecated public void writeToParcel(android.os.Parcel, int);
+ field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallAttributes> CREATOR;
}
public final class CallForwardingInfo implements android.os.Parcelable {
@@ -12912,6 +12963,28 @@
method @NonNull public android.telephony.CallQuality.Builder setUplinkCallQualityLevel(int);
}
+ public final class CallState implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.telephony.CallQuality getCallQuality();
+ method public int getCallState();
+ method public int getImsCallServiceType();
+ method @Nullable public String getImsCallSessionId();
+ method public int getImsCallType();
+ method public int getNetworkType();
+ method public void writeToParcel(@Nullable android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CallState> CREATOR;
+ }
+
+ public static final class CallState.Builder {
+ ctor public CallState.Builder(int);
+ method @NonNull public android.telephony.CallState build();
+ method @NonNull public android.telephony.CallState.Builder setCallQuality(@Nullable android.telephony.CallQuality);
+ method @NonNull public android.telephony.CallState.Builder setImsCallServiceType(int);
+ method @NonNull public android.telephony.CallState.Builder setImsCallSessionId(@Nullable String);
+ method @NonNull public android.telephony.CallState.Builder setImsCallType(int);
+ method @NonNull public android.telephony.CallState.Builder setNetworkType(int);
+ }
+
public class CarrierConfigManager {
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getDefaultCarrierServicePackageName();
method @NonNull public static android.os.PersistableBundle getDefaultConfig();
@@ -13662,7 +13735,8 @@
}
public static interface TelephonyCallback.CallAttributesListener {
- method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallAttributesChanged(@NonNull android.telephony.CallAttributes);
+ method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public default void onCallStatesChanged(@NonNull java.util.List<android.telephony.CallState>);
}
public static interface TelephonyCallback.DataEnabledListener {
@@ -14763,6 +14837,7 @@
field public static final int CALL_RESTRICT_CAUSE_HD = 3; // 0x3
field public static final int CALL_RESTRICT_CAUSE_NONE = 0; // 0x0
field public static final int CALL_RESTRICT_CAUSE_RAT = 1; // 0x1
+ field public static final int CALL_TYPE_NONE = 0; // 0x0
field public static final int CALL_TYPE_VIDEO_N_VOICE = 3; // 0x3
field public static final int CALL_TYPE_VOICE = 2; // 0x2
field public static final int CALL_TYPE_VOICE_N_VIDEO = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 35e01f1..984e822 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1039,6 +1039,7 @@
method @NonNull public static android.util.Pair<java.util.List<android.graphics.Typeface>,java.util.List<android.graphics.Typeface>> changeDefaultFontForTest(@NonNull java.util.List<android.graphics.Typeface>, @NonNull java.util.List<android.graphics.Typeface>);
method @NonNull public static long[] deserializeFontMap(@NonNull java.nio.ByteBuffer, @NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws java.io.IOException;
method @Nullable public static android.os.SharedMemory getSystemFontMapSharedMemory();
+ method public void releaseNativeObjectForTest();
method @NonNull public static android.os.SharedMemory serializeFontMap(@NonNull java.util.Map<java.lang.String,android.graphics.Typeface>) throws android.system.ErrnoException, java.io.IOException;
}
@@ -1251,6 +1252,7 @@
field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
field public static final int SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY = 3; // 0x3
field public static final int SWITCHING_TYPE_WITHIN_GROUPS = 1; // 0x1
+ field public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 16384; // 0x4000
field public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 512; // 0x200
}
@@ -1907,6 +1909,7 @@
}
public abstract class VibrationEffect implements android.os.Parcelable {
+ method @Nullable public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
method public static android.os.VibrationEffect get(int);
method public static android.os.VibrationEffect get(int, boolean);
method @Nullable public static android.os.VibrationEffect get(android.net.Uri, android.content.Context);
@@ -1921,6 +1924,7 @@
}
public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
+ method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
method public long getDuration();
method public int getRepeatIndex();
method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
@@ -2884,6 +2888,10 @@
method @NonNull public android.view.Display.Mode.Builder setResolution(int, int);
}
+ public final class DisplayShape implements android.os.Parcelable {
+ method @NonNull public static android.view.DisplayShape fromSpecString(@NonNull String, float, int, int);
+ }
+
public class FocusFinder {
method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
}
diff --git a/core/java/android/accounts/ChooseTypeAndAccountActivity.java b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
index 4d4a4d7..e447d86 100644
--- a/core/java/android/accounts/ChooseTypeAndAccountActivity.java
+++ b/core/java/android/accounts/ChooseTypeAndAccountActivity.java
@@ -402,7 +402,7 @@
mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
mCallingUid);
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivityForResult(intent, REQUEST_ADD_ACCOUNT);
+ startActivityForResult(new Intent(intent), REQUEST_ADD_ACCOUNT);
return;
}
} catch (OperationCanceledException e) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 884870b..96ced41 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,6 +502,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
+ private long mStartSeq; // Only accesssed from the main thread
Bundle mCoreSettings = null;
@@ -6809,6 +6810,14 @@
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+
+ final IActivityManager mgr = ActivityManager.getService();
+ try {
+ mgr.finishAttachApplication(mStartSeq);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7649,6 +7658,8 @@
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
+ mStartSeq = startSeq;
+
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f25e639..9d5c01a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -767,11 +767,11 @@
*/
@SystemApi
public void setDeliveryGroupMatchingKey(@NonNull String namespace, @NonNull String key) {
- Preconditions.checkArgument(!namespace.contains("/"),
- "namespace should not contain '/'");
- Preconditions.checkArgument(!key.contains("/"),
- "key should not contain '/'");
- mDeliveryGroupMatchingKey = namespace + "/" + key;
+ Preconditions.checkArgument(!namespace.contains(":"),
+ "namespace should not contain ':'");
+ Preconditions.checkArgument(!key.contains(":"),
+ "key should not contain ':'");
+ mDeliveryGroupMatchingKey = namespace + ":" + key;
}
/**
@@ -779,7 +779,7 @@
* broadcast belongs to.
*
* @return the delivery group namespace and key that was previously set using
- * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code /}.
+ * {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
* @hide
*/
@SystemApi
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index 9bf8550..63fdc2e 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -48,6 +48,7 @@
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.Overridable;
import android.content.Context;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.ServiceInfo.ForegroundServiceType;
@@ -879,7 +880,8 @@
int checkPermission(@NonNull Context context, @NonNull String name, int callerUid,
int callerPid, String packageName, boolean allowWhileInUse) {
// Simple case, check if it's already granted.
- if (context.checkPermission(name, callerPid, callerUid) == PERMISSION_GRANTED) {
+ if (PermissionChecker.checkPermissionForPreflight(context, name,
+ callerPid, callerUid, packageName) == PERMISSION_GRANTED) {
return PERMISSION_GRANTED;
}
if (allowWhileInUse) {
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index f92194d..2f51b17 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -213,7 +213,7 @@
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
public @GameMode int[] getAvailableGameModes(@NonNull String packageName) {
try {
- return mService.getAvailableGameModes(packageName);
+ return mService.getAvailableGameModes(packageName, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 902f172..3edaabd 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,6 +147,7 @@
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
+ void finishAttachApplication(long startSeq);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -718,8 +719,8 @@
/**
* Control the app freezer state. Returns true in case of success, false if the operation
- * didn't succeed (for example, when the app freezer isn't supported).
- * Handling the freezer state via this method is reentrant, that is it can be
+ * didn't succeed (for example, when the app freezer isn't supported).
+ * Handling the freezer state via this method is reentrant, that is it can be
* disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
* enable match, the freezer is re-enabled at last enable only.
* @param enable set it to true to enable the app freezer, false to disable it.
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index aea097d..3d6ab6f 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -29,7 +29,7 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
void setGameMode(String packageName, int gameMode, int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
- int[] getAvailableGameModes(String packageName);
+ int[] getAvailableGameModes(String packageName, int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
boolean isAngleEnabled(String packageName, int userId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_GAME_MODE)")
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 6d7a161..3a7d483 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -1128,12 +1128,10 @@
/**
* Callback called on timeout for {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ * See {@link ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE} for more details.
*
- * TODO Implement it
- * TODO Javadoc
- *
- * @param startId
- * @hide
+ * @param startId the startId passed to {@link #onStartCommand(Intent, int, int)} when
+ * the service started.
*/
public void onTimeout(int startId) {
}
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 295d69d..0837d85 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -19,6 +19,9 @@
import android.app.PendingIntent;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualKeyEvent;
@@ -97,6 +100,24 @@
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
+ * Creates a virtual sensor, capable of injecting sensor events into the system.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
+
+ /**
+ * Removes the sensor corresponding to the given token from the system.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ void unregisterSensor(IBinder token);
+
+ /**
+ * Sends an event to the virtual sensor corresponding to the given token.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+ boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
+
+ /**
* Launches a pending intent on the given display that is owned by this virtual device.
*/
void launchPendingIntent(
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 9154701..01b42bf 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -22,12 +22,15 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
+import android.companion.virtual.sensor.VirtualSensor;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
@@ -58,6 +61,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -76,7 +80,8 @@
| DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
| DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
| DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
- | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
/**
* The default device ID, which is the ID of the primary (non-virtual) device.
@@ -88,6 +93,26 @@
*/
public static final int INVALID_DEVICE_ID = -1;
+ /**
+ * Broadcast Action: A Virtual Device was removed.
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
+ "android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
+
+ /**
+ * Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
+ * Contains the identifier of the virtual device, which was removed.
+ *
+ * @hide
+ */
+ public static final String EXTRA_VIRTUAL_DEVICE_ID =
+ "android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
@@ -250,7 +275,10 @@
};
@Nullable
private VirtualAudioDevice mVirtualAudioDevice;
+ @NonNull
+ private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private VirtualDevice(
IVirtualDeviceManager service,
Context context,
@@ -264,6 +292,10 @@
associationId,
params,
mActivityListenerBinder);
+ final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
+ for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
+ mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
+ }
}
/**
@@ -278,6 +310,23 @@
}
/**
+ * Returns this device's sensor with the given type and name, if any.
+ *
+ * @see VirtualDeviceParams.Builder#addVirtualSensorConfig
+ *
+ * @param type The type of the sensor.
+ * @param name The name of the sensor.
+ * @return The matching sensor if found, {@code null} otherwise.
+ */
+ @Nullable
+ public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
+ return mVirtualSensors.stream()
+ .filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
+ .findAny()
+ .orElse(null);
+ }
+
+ /**
* Launches a given pending intent on the give display ID.
*
* @param displayId The display to launch the pending intent on. This display must be
@@ -437,6 +486,7 @@
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
try {
+ // This also takes care of unregistering all virtual sensors.
mVirtualDevice.close();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -622,6 +672,28 @@
}
/**
+ * Creates a virtual sensor, capable of injecting sensor events into the system. Only for
+ * internal use, since device sensors must remain valid for the entire lifetime of the
+ * device.
+ *
+ * @param config The configuration of the sensor.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ @NonNull
+ public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(config);
+ try {
+ final IBinder token = new Binder(
+ "android.hardware.sensor.VirtualSensor:" + config.getName());
+ mVirtualDevice.createVirtualSensor(token, config);
+ return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.
*
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f8c2e34a..e2b5c5d 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,20 +23,22 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
+import android.util.SparseArray;
import android.util.SparseIntArray;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +160,7 @@
@Nullable private final String mName;
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
+ @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
private VirtualDeviceParams(
@LockState int lockState,
@@ -169,24 +172,22 @@
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@Nullable String name,
- @NonNull SparseIntArray devicePolicies) {
- Preconditions.checkNotNull(usersWithMatchingAccounts);
- Preconditions.checkNotNull(allowedCrossTaskNavigations);
- Preconditions.checkNotNull(blockedCrossTaskNavigations);
- Preconditions.checkNotNull(allowedActivities);
- Preconditions.checkNotNull(blockedActivities);
- Preconditions.checkNotNull(devicePolicies);
-
+ @NonNull SparseIntArray devicePolicies,
+ @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
mLockState = lockState;
- mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
- mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
- mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
+ mUsersWithMatchingAccounts =
+ new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
+ mAllowedCrossTaskNavigations =
+ new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
+ mBlockedCrossTaskNavigations =
+ new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
mDefaultNavigationPolicy = defaultNavigationPolicy;
- mAllowedActivities = new ArraySet<>(allowedActivities);
- mBlockedActivities = new ArraySet<>(blockedActivities);
+ mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
+ mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
mDefaultActivityPolicy = defaultActivityPolicy;
mName = name;
- mDevicePolicies = devicePolicies;
+ mDevicePolicies = Objects.requireNonNull(devicePolicies);
+ mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
}
@SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@
mDefaultActivityPolicy = parcel.readInt();
mName = parcel.readString8();
mDevicePolicies = parcel.readSparseIntArray();
+ mVirtualSensorConfigs = new ArrayList<>();
+ parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
}
/**
@@ -310,12 +313,21 @@
* Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no
* policy for this type has been explicitly specified.
*
- * @see Builder#addDevicePolicy
+ * @see Builder#setDevicePolicy
*/
public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) {
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
}
+ /**
+ * Returns the configurations for all sensors that should be created for this device.
+ *
+ * @see Builder#addVirtualSensorConfig
+ */
+ public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
+ return mVirtualSensorConfigs;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -333,6 +345,7 @@
dest.writeInt(mDefaultActivityPolicy);
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
+ dest.writeTypedList(mVirtualSensorConfigs);
}
@Override
@@ -428,6 +441,7 @@
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
+ @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@
@NonNull
public Builder setUsersWithMatchingAccounts(
@NonNull Set<UserHandle> usersWithMatchingAccounts) {
- Preconditions.checkNotNull(usersWithMatchingAccounts);
- mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+ mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
return this;
}
@@ -491,7 +504,6 @@
@NonNull
public Builder setAllowedCrossTaskNavigations(
@NonNull Set<ComponentName> allowedCrossTaskNavigations) {
- Preconditions.checkNotNull(allowedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -500,7 +512,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
mDefaultNavigationPolicyConfigured = true;
- mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
+ mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
return this;
}
@@ -523,7 +535,6 @@
@NonNull
public Builder setBlockedCrossTaskNavigations(
@NonNull Set<ComponentName> blockedCrossTaskNavigations) {
- Preconditions.checkNotNull(blockedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -532,7 +543,7 @@
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
mDefaultNavigationPolicyConfigured = true;
- mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
+ mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
return this;
}
@@ -551,7 +562,6 @@
*/
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
- Preconditions.checkNotNull(allowedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -559,7 +569,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
mDefaultActivityPolicyConfigured = true;
- mAllowedActivities = allowedActivities;
+ mAllowedActivities = Objects.requireNonNull(allowedActivities);
return this;
}
@@ -578,7 +588,6 @@
*/
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
- Preconditions.checkNotNull(blockedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -586,7 +595,7 @@
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
mDefaultActivityPolicyConfigured = true;
- mBlockedActivities = blockedActivities;
+ mBlockedActivities = Objects.requireNonNull(blockedActivities);
return this;
}
@@ -606,7 +615,7 @@
}
/**
- * Add a policy for this virtual device.
+ * Specifies a policy for this virtual device.
*
* Policies define the system behavior that may be specific for this virtual device. A
* policy can be defined for each {@code PolicyType}, but they are all optional.
@@ -615,16 +624,55 @@
* @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
*/
@NonNull
- public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
+ public Builder setDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) {
mDevicePolicies.put(policyType, devicePolicy);
return this;
}
/**
+ * Adds a configuration for a sensor that should be created for this virtual device.
+ *
+ * <p>Device sensors must remain valid for the entire lifetime of the device, hence they are
+ * created together with the device itself, and removed when the device is removed.
+ *
+ * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
+ *
+ * @see android.companion.virtual.sensor.VirtualSensor
+ * @see #setDevicePolicy
+ */
+ @NonNull
+ public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
+ mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
+ *
+ * @throws IllegalArgumentException if there's mismatch between policy definition and
+ * the passed parameters or if there are sensor configs with the same type and name.
+ *
*/
@NonNull
public VirtualDeviceParams build() {
+ if (!mVirtualSensorConfigs.isEmpty()
+ && (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
+ != DEVICE_POLICY_CUSTOM)) {
+ throw new IllegalArgumentException(
+ "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+ + "virtual sensors.");
+ }
+ SparseArray<Set<String>> sensorNameByType = new SparseArray();
+ for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
+ VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
+ Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
+ if (!sensorNames.add(config.getName())) {
+ throw new IllegalArgumentException(
+ "Sensor names must be unique for a particular sensor type.");
+ }
+ sensorNameByType.put(config.getType(), sensorNames);
+ }
+
return new VirtualDeviceParams(
mLockState,
mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@
mBlockedActivities,
mDefaultActivityPolicy,
mName,
- mDevicePolicies);
+ mDevicePolicies,
+ mVirtualSensorConfigs);
}
}
}
diff --git a/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
new file mode 100644
index 0000000..b99cc7e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/IVirtualSensorStateChangeCallback.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+/**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ *
+ * @hide
+ */
+oneway interface IVirtualSensorStateChangeCallback {
+
+ /**
+ * Called when the registered listeners to a virtual sensor have changed.
+ *
+ * @param enabled Whether the sensor is enabled.
+ * @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
+ * @param batchReportingLatencyMicros The requested maximum time interval in microseconds
+ * between the delivery of two batches of sensor events.
+ */
+ void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
new file mode 100644
index 0000000..a184481
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.companion.virtual.IVirtualDevice;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.time.Duration;
+
+/**
+ * Representation of a sensor on a remote device, capable of sending events, such as an
+ * accelerometer or a gyroscope.
+ *
+ * This registers the sensor device with the sensor framework as a runtime sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public class VirtualSensor {
+
+ /**
+ * Interface for notification of listener registration changes for a virtual sensor.
+ */
+ public interface SensorStateChangeCallback {
+ /**
+ * Called when the registered listeners to a virtual sensor have changed.
+ *
+ * @param enabled Whether the sensor is enabled.
+ * @param samplingPeriod The requested sampling period of the sensor.
+ * @param batchReportLatency The requested maximum time interval between the delivery of two
+ * batches of sensor events.
+ */
+ void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
+ @NonNull Duration batchReportLatency);
+ }
+
+ private final int mType;
+ private final String mName;
+ private final IVirtualDevice mVirtualDevice;
+ private final IBinder mToken;
+
+ /**
+ * @hide
+ */
+ public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
+ mType = type;
+ mName = name;
+ mVirtualDevice = virtualDevice;
+ mToken = token;
+ }
+
+ /**
+ * Returns the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the sensor.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Send a sensor event to the system.
+ */
+ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+ try {
+ mVirtualDevice.sendSensorEvent(mToken, event);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
new file mode 100644
index 0000000..48b463a
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorConfig;
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
new file mode 100644
index 0000000..7982fa5
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.time.Duration;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Configuration for creation of a virtual sensor.
+ * @see VirtualSensor
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorConfig implements Parcelable {
+
+ private final int mType;
+ @NonNull
+ private final String mName;
+ @Nullable
+ private final String mVendor;
+ @Nullable
+ private final IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+ private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
+ @Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
+ mType = type;
+ mName = name;
+ mVendor = vendor;
+ mStateChangeCallback = stateChangeCallback;
+ }
+
+ private VirtualSensorConfig(@NonNull Parcel parcel) {
+ mType = parcel.readInt();
+ mName = parcel.readString8();
+ mVendor = parcel.readString8();
+ mStateChangeCallback =
+ IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeString8(mName);
+ parcel.writeString8(mVendor);
+ parcel.writeStrongBinder(
+ mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
+ }
+
+ /**
+ * Returns the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the name of the sensor, which must be unique per sensor type for each virtual device.
+ */
+ @NonNull
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the vendor string of the sensor.
+ * @see Builder#setVendor
+ */
+ @Nullable
+ public String getVendor() {
+ return mVendor;
+ }
+
+ /**
+ * Returns the callback to get notified about changes in the sensor listeners.
+ * @hide
+ */
+ @Nullable
+ public IVirtualSensorStateChangeCallback getStateChangeCallback() {
+ return mStateChangeCallback;
+ }
+
+ /**
+ * Builder for {@link VirtualSensorConfig}.
+ */
+ public static final class Builder {
+
+ private final int mType;
+ @NonNull
+ private final String mName;
+ @Nullable
+ private String mVendor;
+ @Nullable
+ private IVirtualSensorStateChangeCallback mStateChangeCallback;
+
+ private static class SensorStateChangeCallbackDelegate
+ extends IVirtualSensorStateChangeCallback.Stub {
+ @NonNull
+ private final Executor mExecutor;
+ @NonNull
+ private final VirtualSensor.SensorStateChangeCallback mCallback;
+
+ SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+ @Override
+ public void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros) {
+ final Duration samplingPeriod =
+ Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
+ final Duration batchReportingLatency =
+ Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
+ mExecutor.execute(() -> mCallback.onStateChanged(
+ enabled, samplingPeriod, batchReportingLatency));
+ }
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param type The
+ * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+ * @param name The name of the sensor. Must be unique among all sensors with the same type
+ * that belong to the same virtual device.
+ */
+ public Builder(int type, @NonNull String name) {
+ mType = type;
+ mName = Objects.requireNonNull(name);
+ }
+
+ /**
+ * Creates a new {@link VirtualSensorConfig}.
+ */
+ @NonNull
+ public VirtualSensorConfig build() {
+ return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
+ }
+
+ /**
+ * Sets the vendor string of the sensor.
+ */
+ @NonNull
+ public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
+ mVendor = vendor;
+ return this;
+ }
+
+ /**
+ * Sets the callback to get notified about changes in the sensor listeners.
+ *
+ * @param executor The executor where the callback is executed on.
+ * @param callback The callback to get notified when the state of the sensor
+ * listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
+ */
+ @SuppressLint("MissingGetterMatchingBuilder")
+ @NonNull
+ public VirtualSensorConfig.Builder setStateChangeCallback(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull VirtualSensor.SensorStateChangeCallback callback) {
+ mStateChangeCallback = new SensorStateChangeCallbackDelegate(
+ Objects.requireNonNull(executor),
+ Objects.requireNonNull(callback));
+ return this;
+ }
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualSensorConfig createFromParcel(Parcel source) {
+ return new VirtualSensorConfig(source);
+ }
+
+ public VirtualSensorConfig[] newArray(int size) {
+ return new VirtualSensorConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
new file mode 100644
index 0000000..9943946
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+parcelable VirtualSensorEvent;
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
new file mode 100644
index 0000000..8f8860e
--- /dev/null
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+
+/**
+ * A sensor event that originated from a virtual device's sensor.
+ *
+ * @hide
+ */
+@SystemApi
+public final class VirtualSensorEvent implements Parcelable {
+
+ @NonNull
+ private float[] mValues;
+ private long mTimestampNanos;
+
+ private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
+ mValues = values;
+ mTimestampNanos = timestampNanos;
+ }
+
+ private VirtualSensorEvent(@NonNull Parcel parcel) {
+ final int valuesLength = parcel.readInt();
+ mValues = new float[valuesLength];
+ parcel.readFloatArray(mValues);
+ mTimestampNanos = parcel.readLong();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
+ parcel.writeInt(mValues.length);
+ parcel.writeFloatArray(mValues);
+ parcel.writeLong(mTimestampNanos);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Returns the values of this sensor event. The length and contents depend on the
+ * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+ * @see android.hardware.SensorEvent#values
+ */
+ @NonNull
+ public float[] getValues() {
+ return mValues;
+ }
+
+ /**
+ * The time in nanoseconds at which the event happened. For a given sensor, each new sensor
+ * event should be monotonically increasing.
+ *
+ * @see Builder#setTimestampNanos(long)
+ */
+ public long getTimestampNanos() {
+ return mTimestampNanos;
+ }
+
+ /**
+ * Builder for {@link VirtualSensorEvent}.
+ */
+ public static final class Builder {
+
+ @NonNull
+ private float[] mValues;
+ private long mTimestampNanos = 0;
+
+ /**
+ * Creates a new builder.
+ * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+ */
+ public Builder(@NonNull float[] values) {
+ mValues = values;
+ }
+
+ /**
+ * Creates a new {@link VirtualSensorEvent}.
+ */
+ @NonNull
+ public VirtualSensorEvent build() {
+ if (mValues == null || mValues.length == 0) {
+ throw new IllegalArgumentException(
+ "Cannot build virtual sensor event with no values.");
+ }
+ if (mTimestampNanos <= 0) {
+ mTimestampNanos = SystemClock.elapsedRealtimeNanos();
+ }
+ return new VirtualSensorEvent(mValues, mTimestampNanos);
+ }
+
+ /**
+ * Sets the timestamp of this event. For a given sensor, each new sensor event should be
+ * monotonically increasing using the same time base as
+ * {@link android.os.SystemClock#elapsedRealtimeNanos()}.
+ *
+ * If not explicitly set, the current timestamp is used for the sensor event.
+ *
+ * @see android.hardware.SensorEvent#timestamp
+ */
+ @NonNull
+ public Builder setTimestampNanos(long timestampNanos) {
+ mTimestampNanos = timestampNanos;
+ return this;
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
+ new Parcelable.Creator<>() {
+ public VirtualSensorEvent createFromParcel(Parcel source) {
+ return new VirtualSensorEvent(source);
+ }
+
+ public VirtualSensorEvent[] newArray(int size) {
+ return new VirtualSensorEvent[size];
+ }
+ };
+}
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index cc7977a..99fc5a3 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -16,15 +16,21 @@
package android.content.om;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.FabricatedOverlayInternal;
import android.os.FabricatedOverlayInternalEntry;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
+import android.util.TypedValue;
+import com.android.internal.content.om.OverlayManagerImpl;
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
@@ -82,8 +88,24 @@
}
/**
+ * Constructs a builder for building a fabricated overlay.
+ *
+ * @param name a name used to uniquely identify the fabricated overlay owned by the caller
+ * itself.
+ * @param targetPackage the name of the package to overlay
+ */
+ public Builder(@NonNull String name, @NonNull String targetPackage) {
+ mName = OverlayManagerImpl.checkOverlayNameValid(name);
+ mTargetPackage =
+ Preconditions.checkStringNotEmpty(
+ targetPackage, "'targetPackage' must not be empty nor null");
+ mOwningPackage = ""; // The package name is filled in OverlayManager.commit
+ }
+
+ /**
* Sets the name of the overlayable resources to overlay (can be null).
*/
+ @NonNull
public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
mTargetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
return this;
@@ -111,45 +133,110 @@
}
/**
- * Sets the value of the fabricated overlay
+ * Sets the value of the fabricated overlay for the integer-like types.
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param dataType the data type of the new value
* @param value the unsigned 32 bit integer representing the new value
- *
+ * @return the builder itself
+ * @see #setResourceValue(String, int, int, String)
* @see android.util.TypedValue#type
*/
- public Builder setResourceValue(@NonNull String resourceName, int dataType, int value) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+ int dataType,
+ int value) {
+ return setResourceValue(resourceName, dataType, value, null /* configuration */);
+ }
+
+ /**
+ * Sets the value of the fabricated overlay for the integer-like types with the
+ * configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the unsigned 32 bit integer representing the new value
+ * @param configuration The string representation of the config this overlay is enabled for
+ * @see android.util.TypedValue#type
+ */
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT)
+ int dataType,
+ int value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.dataType = dataType;
+ entry.dataType =
+ Preconditions.checkArgumentInRange(
+ dataType,
+ TypedValue.TYPE_FIRST_INT,
+ TypedValue.TYPE_LAST_INT,
+ "dataType");
entry.data = value;
+ entry.configuration = configuration;
mEntries.add(entry);
return this;
}
+ /** @hide */
+ @IntDef(
+ prefix = {"OVERLAY_TYPE"},
+ value = {
+ TypedValue.TYPE_STRING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StringTypeOverlayResource {}
+
/**
- * Sets the value of the fabricated overlay
+ * Sets the value of the fabricated overlay for the string-like type.
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param dataType the data type of the new value
- * @param value the unsigned 32 bit integer representing the new value
- * @param configuration The string representation of the config this overlay is enabled for
- *
+ * @param value the string representing the new value
+ * @return the builder itself
* @see android.util.TypedValue#type
*/
- public Builder setResourceValue(@NonNull String resourceName, int dataType, int value,
- String configuration) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @StringTypeOverlayResource int dataType,
+ @NonNull String value) {
+ return setResourceValue(resourceName, dataType, value, null /* configuration */);
+ }
+
+ /**
+ * Sets the value of the fabricated overlay for the string-like type with the configuration.
+ *
+ * @param resourceName name of the target resource to overlay (in the form
+ * [package]:type/entry)
+ * @param dataType the data type of the new value
+ * @param value the string representing the new value
+ * @param configuration The string representation of the config this overlay is enabled for
+ * @see android.util.TypedValue#type
+ */
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @StringTypeOverlayResource int dataType,
+ @NonNull String value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.data = value;
+ entry.dataType =
+ Preconditions.checkArgumentInRange(
+ dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+ entry.stringData = Objects.requireNonNull(value);
entry.configuration = configuration;
mEntries.add(entry);
return this;
@@ -159,68 +246,32 @@
* Sets the value of the fabricated overlay
*
* @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
- * @param dataType the data type of the new value
- * @param value the string representing the new value
- *
- * @see android.util.TypedValue#type
- */
- public Builder setResourceValue(@NonNull String resourceName, int dataType, String value) {
- ensureValidResourceName(resourceName);
-
- final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
- entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.stringData = value;
- mEntries.add(entry);
- return this;
- }
-
- /**
- * Sets the value of the fabricated overlay
- *
- * @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
- * @param dataType the data type of the new value
- * @param value the string representing the new value
- * @param configuration The string representation of the config this overlay is enabled for
- *
- * @see android.util.TypedValue#type
- */
- public Builder setResourceValue(@NonNull String resourceName, int dataType, String value,
- String configuration) {
- ensureValidResourceName(resourceName);
-
- final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
- entry.resourceName = resourceName;
- entry.dataType = dataType;
- entry.stringData = value;
- entry.configuration = configuration;
- mEntries.add(entry);
- return this;
- }
-
- /**
- * Sets the value of the fabricated overlay
- *
- * @param resourceName name of the target resource to overlay (in the form
- * [package]:type/entry)
+ * [package]:type/entry)
* @param value the file descriptor whose contents are the value of the frro
* @param configuration The string representation of the config this overlay is enabled for
+ * @return the builder itself
*/
- public Builder setResourceValue(@NonNull String resourceName, ParcelFileDescriptor value,
- String configuration) {
+ @NonNull
+ public Builder setResourceValue(
+ @NonNull String resourceName,
+ @NonNull ParcelFileDescriptor value,
+ @Nullable String configuration) {
ensureValidResourceName(resourceName);
final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
entry.resourceName = resourceName;
- entry.binaryData = value;
+ entry.binaryData = Objects.requireNonNull(value);
entry.configuration = configuration;
mEntries.add(entry);
return this;
}
- /** Builds an immutable fabricated overlay. */
+ /**
+ * Builds an immutable fabricated overlay.
+ *
+ * @return the fabricated overlay
+ */
+ @NonNull
public FabricatedOverlay build() {
final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
overlay.packageName = mOwningPackage;
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 812f6b0..ed1f6a2 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -17,6 +17,7 @@
package android.content.om;
import android.annotation.NonNull;
+import android.annotation.NonUiContext;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
@@ -25,12 +26,16 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import com.android.internal.content.om.OverlayManagerImpl;
+
+import java.io.IOException;
import java.util.List;
/**
@@ -76,6 +81,7 @@
private final IOverlayManager mService;
private final Context mContext;
+ private final OverlayManagerImpl mOverlayManagerImpl;
/**
* Pre R a {@link java.lang.SecurityException} would only be thrown by setEnabled APIs (e
@@ -117,6 +123,7 @@
public OverlayManager(Context context, IOverlayManager service) {
mContext = context;
mService = service;
+ mOverlayManagerImpl = new OverlayManagerImpl(context);
}
/** @hide */
@@ -301,6 +308,17 @@
* @hide
*/
public void commit(@NonNull final OverlayManagerTransaction transaction) {
+ if (transaction.isSelfTargetingTransaction()
+ || mService == null
+ || mService.asBinder() == null) {
+ try {
+ commitSelfTarget(transaction);
+ } catch (PackageManager.NameNotFoundException | IOException e) {
+ throw new RuntimeException(e);
+ }
+ return;
+ }
+
try {
mService.commit(transaction);
} catch (RemoteException e) {
@@ -332,4 +350,48 @@
throw e;
}
}
+
+ /**
+ * Get a OverlayManagerTransaction.Builder to build out a overlay manager transaction.
+ *
+ * @return a builder of the overlay manager transaction.
+ * @hide
+ */
+ @NonNull
+ public OverlayManagerTransaction.Builder beginTransaction() {
+ return new OverlayManagerTransaction.Builder(this);
+ }
+
+ /**
+ * Commit the self-targeting transaction to register or unregister overlays.
+ *
+ * <p>Applications can request OverlayManager to register overlays and unregister the registered
+ * overlays via {@link OverlayManagerTransaction}.
+ *
+ * @throws IOException if there is a file operation error.
+ * @throws PackageManager.NameNotFoundException if the package name is not found.
+ * @hide
+ */
+ @NonUiContext
+ void commitSelfTarget(@NonNull final OverlayManagerTransaction transaction)
+ throws PackageManager.NameNotFoundException, IOException {
+ synchronized (mOverlayManagerImpl) {
+ mOverlayManagerImpl.commit(transaction);
+ }
+ }
+
+ /**
+ * Get the related information of overlays for {@code targetPackageName}.
+ *
+ * @param targetPackageName the target package name
+ * @return a list of overlay information
+ * @hide
+ */
+ @NonNull
+ @NonUiContext
+ public List<OverlayInfo> getOverlayInfosForTarget(@NonNull final String targetPackageName) {
+ synchronized (mOverlayManagerImpl) {
+ return mOverlayManagerImpl.getOverlayInfosForTarget(targetPackageName);
+ }
+ }
}
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 868dab2..42b3ef3 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -20,19 +20,22 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.NonUiContext;
import android.annotation.Nullable;
-import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
/**
* Container for a batch of requests to the OverlayManagerService.
@@ -53,13 +56,16 @@
// TODO: remove @hide from this class when OverlayManager is added to the
// SDK, but keep OverlayManagerTransaction.Request @hidden
private final List<Request> mRequests;
+ private final OverlayManager mOverlayManager;
- OverlayManagerTransaction(@NonNull final List<Request> requests) {
+ OverlayManagerTransaction(
+ @NonNull final List<Request> requests, @Nullable OverlayManager overlayManager) {
checkNotNull(requests);
if (requests.contains(null)) {
throw new IllegalArgumentException("null request");
}
mRequests = requests;
+ mOverlayManager = overlayManager;
}
private OverlayManagerTransaction(@NonNull final Parcel source) {
@@ -72,6 +78,7 @@
final Bundle extras = source.readBundle(null);
mRequests.add(new Request(request, overlay, userId, extras));
}
+ mOverlayManager = null;
}
@Override
@@ -156,6 +163,20 @@
*/
public static class Builder {
private final List<Request> mRequests = new ArrayList<>();
+ @Nullable private final OverlayManager mOverlayManager;
+
+ public Builder() {
+ mOverlayManager = null;
+ }
+
+ /**
+ * The transaction builder for self-targeting.
+ *
+ * @param overlayManager is not null if the transaction is for self-targeting.
+ */
+ Builder(@NonNull OverlayManager overlayManager) {
+ mOverlayManager = Objects.requireNonNull(overlayManager);
+ }
/**
* Request that an overlay package be enabled and change its loading
@@ -205,7 +226,10 @@
*
* @hide
*/
+ @NonNull
public Builder registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
+ Objects.requireNonNull(overlay);
+
final Bundle extras = new Bundle();
extras.putParcelable(Request.BUNDLE_FABRICATED_OVERLAY, overlay.mOverlay);
mRequests.add(new Request(Request.TYPE_REGISTER_FABRICATED, overlay.getIdentifier(),
@@ -220,7 +244,10 @@
*
* @hide
*/
+ @NonNull
public Builder unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
+ Objects.requireNonNull(overlay);
+
mRequests.add(new Request(Request.TYPE_UNREGISTER_FABRICATED, overlay,
UserHandle.USER_ALL));
return this;
@@ -233,8 +260,9 @@
* @see OverlayManager#commit
* @return a new transaction
*/
+ @NonNull
public OverlayManagerTransaction build() {
- return new OverlayManagerTransaction(mRequests);
+ return new OverlayManagerTransaction(mRequests, mOverlayManager);
}
}
@@ -269,4 +297,23 @@
return new OverlayManagerTransaction[size];
}
};
+
+ /**
+ * Commit the overlay manager transaction to register or unregister overlays for self-targeting.
+ *
+ * <p>Applications can register overlays and unregister the registered overlays via {@link
+ * OverlayManagerTransaction}.
+ *
+ * @throws IOException if there is a file operation error.
+ * @throws PackageManager.NameNotFoundException if the package name is not found.
+ * @hide
+ */
+ @NonUiContext
+ public void commit() throws PackageManager.NameNotFoundException, IOException {
+ mOverlayManager.commitSelfTarget(this);
+ }
+
+ boolean isSelfTargetingTransaction() {
+ return mOverlayManager != null;
+ }
}
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 12911d6..1e928bd 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -23,6 +23,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
import android.content.IntentSender;
+import android.os.RemoteCallback;
import android.graphics.Bitmap;
@@ -66,4 +67,6 @@
void setAllowUnlimitedSilentUpdates(String installerPackageName);
void setSilentUpdatesThrottleTime(long throttleTimeInSeconds);
+ void checkInstallConstraints(String installerPackageName, in List<String> packageNames,
+ in PackageInstaller.InstallConstraints constraints, in RemoteCallback callback);
}
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 833919e..ab9d4f3 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -16,6 +16,7 @@
package android.content.pm;
+parcelable PackageInstaller.InstallConstraints;
parcelable PackageInstaller.SessionParams;
parcelable PackageInstaller.SessionInfo;
parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 3551827..c79f99d 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -36,6 +36,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityManager;
@@ -57,6 +58,7 @@
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.ParcelableException;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -89,6 +91,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Offers the ability to install, upgrade, and remove applications on the
@@ -854,6 +857,29 @@
}
/**
+ * Check if install constraints are satisfied for the given packages.
+ *
+ * Note this query result is just a hint and subject to race because system states could
+ * change anytime in-between this query and committing the session.
+ *
+ * The result is returned by a callback because some constraints might take a long time
+ * to evaluate.
+ */
+ public void checkInstallConstraints(@NonNull List<String> packageNames,
+ @NonNull InstallConstraints constraints,
+ @NonNull Consumer<InstallConstraintsResult> callback) {
+ try {
+ var remoteCallback = new RemoteCallback(b -> {
+ callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+ });
+ mInstaller.checkInstallConstraints(
+ mInstallerPackageName, packageNames, constraints, remoteCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Events for observing session lifecycle.
* <p>
* A typical session lifecycle looks like this:
@@ -3647,4 +3673,362 @@
// End of generated code
}
+
+ /**
+ * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+ */
+ @DataClass(genParcelable = true, genHiddenConstructor = true)
+ public static final class InstallConstraintsResult implements Parcelable {
+ /**
+ * True if all constraints are satisfied.
+ */
+ private boolean mAllConstraintsSatisfied;
+
+
+
+ // 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/content/pm/PackageInstaller.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstallConstraintsResult.
+ *
+ * @param allConstraintsSatisfied
+ * True if all constraints are satisfied.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public InstallConstraintsResult(
+ boolean allConstraintsSatisfied) {
+ this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * True if all constraints are satisfied.
+ */
+ @DataClass.Generated.Member
+ public boolean isAllConstraintsSatisfied() {
+ return mAllConstraintsSatisfied;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mAllConstraintsSatisfied) flg |= 0x1;
+ dest.writeByte(flg);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstallConstraintsResult(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean allConstraintsSatisfied = (flg & 0x1) != 0;
+
+ this.mAllConstraintsSatisfied = allConstraintsSatisfied;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstallConstraintsResult> CREATOR
+ = new Parcelable.Creator<InstallConstraintsResult>() {
+ @Override
+ public InstallConstraintsResult[] newArray(int size) {
+ return new InstallConstraintsResult[size];
+ }
+
+ @Override
+ public InstallConstraintsResult createFromParcel(@NonNull Parcel in) {
+ return new InstallConstraintsResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1668650523745L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+ inputSignatures = "private boolean mAllConstraintsSatisfied\nclass InstallConstraintsResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
+ /**
+ * A class to encapsulate constraints for installation.
+ *
+ * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+ * specifies the conditions to check against for the packages in question. This can be used
+ * by app stores to deliver auto updates without disrupting the user experience (referred as
+ * gentle update) - for example, an app store might hold off updates when it find out the
+ * app to update is interacting with the user.
+ *
+ * Use {@link Builder} to create a new instance and call mutator methods to add constraints.
+ * If no mutators were called, default constraints will be generated which implies no
+ * constraints. It is recommended to use preset constraints which are useful in most
+ * cases.
+ *
+ * For the purpose of gentle update, it is recommended to always use {@link #GENTLE_UPDATE}
+ * for the system knows best how to do it. It will also benefits the installer as the
+ * platform evolves and add more constraints to improve the accuracy and efficiency of
+ * gentle update.
+ *
+ * Note the constraints are applied transitively. If app Foo is used by app Bar (via shared
+ * library or bounded service), the constraints will also be applied to Bar.
+ */
+ @DataClass(genParcelable = true, genHiddenConstructor = true)
+ public static final class InstallConstraints implements Parcelable {
+ /**
+ * Preset constraints suitable for gentle update.
+ */
+ @NonNull
+ public static final InstallConstraints GENTLE_UPDATE =
+ new Builder().requireAppNotInteracting().build();
+
+ private final boolean mRequireDeviceIdle;
+ private final boolean mRequireAppNotForeground;
+ private final boolean mRequireAppNotInteracting;
+ private final boolean mRequireAppNotTopVisible;
+ private final boolean mRequireNotInCall;
+
+ /**
+ * Builder class for constructing {@link InstallConstraints}.
+ */
+ public static final class Builder {
+ private boolean mRequireDeviceIdle;
+ private boolean mRequireAppNotForeground;
+ private boolean mRequireAppNotInteracting;
+ private boolean mRequireAppNotTopVisible;
+ private boolean mRequireNotInCall;
+
+ /**
+ * This constraint requires the device is idle.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireDeviceIdle() {
+ mRequireDeviceIdle = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not in the foreground.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotForeground() {
+ mRequireAppNotForeground = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not interacting with the user.
+ * User interaction includes:
+ * <ul>
+ * <li>playing or recording audio/video</li>
+ * <li>sending or receiving network data</li>
+ * <li>being visible to the user</li>
+ * </ul>
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotInteracting() {
+ mRequireAppNotInteracting = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires the app in question is not top-visible to the user.
+ * A top-visible app is showing UI at the top of the screen that the user is
+ * interacting with.
+ *
+ * Note this constraint is a subset of {@link #requireAppNotForeground()}
+ * because a top-visible app is also a foreground app. This is also a subset
+ * of {@link #requireAppNotInteracting()} because a top-visible app is interacting
+ * with the user.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireAppNotTopVisible() {
+ mRequireAppNotTopVisible = true;
+ return this;
+ }
+
+ /**
+ * This constraint requires there is no ongoing call in the device.
+ */
+ @SuppressLint("BuilderSetStyle")
+ @NonNull
+ public Builder requireNotInCall() {
+ mRequireNotInCall = true;
+ return this;
+ }
+
+ /**
+ * Builds a new {@link InstallConstraints} instance.
+ */
+ @NonNull
+ public InstallConstraints build() {
+ return new InstallConstraints(mRequireDeviceIdle, mRequireAppNotForeground,
+ mRequireAppNotInteracting, mRequireAppNotTopVisible, mRequireNotInCall);
+ }
+ }
+
+
+
+ // 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/content/pm/PackageInstaller.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new InstallConstraints.
+ *
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public InstallConstraints(
+ boolean requireDeviceIdle,
+ boolean requireAppNotForeground,
+ boolean requireAppNotInteracting,
+ boolean requireAppNotTopVisible,
+ boolean requireNotInCall) {
+ this.mRequireDeviceIdle = requireDeviceIdle;
+ this.mRequireAppNotForeground = requireAppNotForeground;
+ this.mRequireAppNotInteracting = requireAppNotInteracting;
+ this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+ this.mRequireNotInCall = requireNotInCall;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireDeviceIdle() {
+ return mRequireDeviceIdle;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotForeground() {
+ return mRequireAppNotForeground;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotInteracting() {
+ return mRequireAppNotInteracting;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireAppNotTopVisible() {
+ return mRequireAppNotTopVisible;
+ }
+
+ @DataClass.Generated.Member
+ public boolean isRequireNotInCall() {
+ return mRequireNotInCall;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mRequireDeviceIdle) flg |= 0x1;
+ if (mRequireAppNotForeground) flg |= 0x2;
+ if (mRequireAppNotInteracting) flg |= 0x4;
+ if (mRequireAppNotTopVisible) flg |= 0x8;
+ if (mRequireNotInCall) flg |= 0x10;
+ dest.writeByte(flg);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InstallConstraints(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ boolean requireDeviceIdle = (flg & 0x1) != 0;
+ boolean requireAppNotForeground = (flg & 0x2) != 0;
+ boolean requireAppNotInteracting = (flg & 0x4) != 0;
+ boolean requireAppNotTopVisible = (flg & 0x8) != 0;
+ boolean requireNotInCall = (flg & 0x10) != 0;
+
+ this.mRequireDeviceIdle = requireDeviceIdle;
+ this.mRequireAppNotForeground = requireAppNotForeground;
+ this.mRequireAppNotInteracting = requireAppNotInteracting;
+ this.mRequireAppNotTopVisible = requireAppNotTopVisible;
+ this.mRequireNotInCall = requireNotInCall;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InstallConstraints> CREATOR
+ = new Parcelable.Creator<InstallConstraints>() {
+ @Override
+ public InstallConstraints[] newArray(int size) {
+ return new InstallConstraints[size];
+ }
+
+ @Override
+ public InstallConstraints createFromParcel(@NonNull Parcel in) {
+ return new InstallConstraints(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1668650523752L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+ inputSignatures = "public static final @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints GENTLE_UPDATE\nprivate final boolean mRequireDeviceIdle\nprivate final boolean mRequireAppNotForeground\nprivate final boolean mRequireAppNotInteracting\nprivate final boolean mRequireAppNotTopVisible\nprivate final boolean mRequireNotInCall\nclass InstallConstraints extends java.lang.Object implements [android.os.Parcelable]\nprivate boolean mRequireDeviceIdle\nprivate boolean mRequireAppNotForeground\nprivate boolean mRequireAppNotInteracting\nprivate boolean mRequireAppNotTopVisible\nprivate boolean mRequireNotInCall\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireDeviceIdle()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotForeground()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotInteracting()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireAppNotTopVisible()\npublic @android.annotation.SuppressLint @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints.Builder requireNotInCall()\npublic @android.annotation.NonNull android.content.pm.PackageInstaller.InstallConstraints build()\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
+
}
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 9e6cf62..7ea6733 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -366,24 +366,48 @@
public static final int FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1 << 10;
/**
- * Foreground service type corresponding to {@code shortService} in
- * the {@link android.R.attr#foregroundServiceType} attribute.
+ * A foreground service type for "short-lived" services, which corresponds to
+ * {@code shortService} in the {@link android.R.attr#foregroundServiceType} attribute in the
+ * manifest.
*
- * TODO Implement it
+ * <p>Unlike other foreground service types, this type is not associated with a specific use
+ * case, and it will not require any special permissions
+ * (besides {@link Manifest.permission#FOREGROUND_SERVICE}).
*
- * TODO Expand the javadoc
+ * However, this type has the following restrictions.
*
- * This type is not associated with specific use cases unlike other types, but this has
- * unique restrictions.
* <ul>
- * <li>Has a timeout
- * <li>Cannot start other foreground services from this
* <li>
+ * The type has a 1 minute timeout.
+ * A foreground service of this type must be stopped within the timeout by
+ * {@link android.app.Service#stopSelf),
+ * or {@link android.content.Context#stopService).
+ * {@link android.app.Service#stopForeground) will also work, which will demote the
+ * service to a "background" service, which will soon be stopped by the system.
+ *
+ * <p>The system will <em>not</em> automatically stop it.
+ *
+ * <p>If the service isn't stopped within the timeout,
+ * {@link android.app.Service#onTimeout(int)} will be called.
+ * If the service is still not stopped after the callback,
+ * the app will be declared an ANR.
+ *
+ * <li>
+ * A foreground service of this type cannot be made "sticky"
+ * (see {@link android.app.Service#START_STICKY}). That is, if an app is killed
+ * due to a crash or out-of memory while it's running a short foregorund-service,
+ * the system will not restart the service.
+ * <li>
+ * Other foreground services cannot be started from short foreground services.
+ * Unlike other foreground service types, when an app is running in the background
+ * while only having a "short" foreground service, it's not allowed to start
+ * other foreground services, due to the restriction describe here:
+ * <a href="/guide/components/foreground-services#background-start-restrictions>
+ * Restrictions on background starts
+ * </a>
* </ul>
*
- * @see Service#onTimeout
- *
- * @hide
+ * @see android.app.Service#onTimeout(int)
*/
public static final int FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 1 << 11;
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 7a5ac8e..143c00d 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -26,6 +26,8 @@
import com.android.internal.annotations.GuardedBy;
+import dalvik.annotation.optimization.CriticalNative;
+
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
@@ -459,7 +461,7 @@
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native @NonNull String nativeGetDebugName(long ptr);
private static native long nativeGetStringBlock(long ptr);
- private static native boolean nativeIsUpToDate(long ptr);
+ @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 463dcac..a5a1fa689 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -18,7 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
@@ -27,11 +30,17 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.content.om.OverlayManagerImpl;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
/**
* Provides methods to load resources data from APKs ({@code .apk}) and resources tables
@@ -63,6 +72,48 @@
}
/**
+ * Creates a ResourcesProvider instance from the specified overlay information.
+ *
+ * <p>In order to enable the registered overlays, an application can create a {@link
+ * ResourcesProvider} instance according to the specified {@link OverlayInfo} instance and put
+ * them into a {@link ResourcesLoader} instance. The application calls {@link
+ * android.content.res.Resources#addLoaders(ResourcesLoader...)} to load the overlays.
+ *
+ * @param overlayInfo is the information about the specified overlay
+ * @return the resources provider instance for the {@code overlayInfo}
+ * @throws IOException when the files can't be loaded.
+ * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
+ * @hide
+ */
+ @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
+ @NonNull
+ public static ResourcesProvider loadOverlay(@NonNull OverlayInfo overlayInfo)
+ throws IOException {
+ Objects.requireNonNull(overlayInfo);
+ Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getTargetOverlayableName(), "Without overlayable name");
+ final String overlayName =
+ OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
+ final String path =
+ Preconditions.checkStringNotEmpty(
+ overlayInfo.getBaseCodePath(), "Invalid base path");
+
+ final Path frroPath = Path.of(path);
+ if (!Files.isRegularFile(frroPath)) {
+ throw new FileNotFoundException("The frro file not found");
+ }
+ final Path idmapPath = frroPath.getParent().resolve(overlayName + ".idmap");
+ if (!Files.isRegularFile(idmapPath)) {
+ throw new FileNotFoundException("The idmap file not found");
+ }
+
+ return new ResourcesProvider(
+ ApkAssets.loadOverlayFromPath(
+ idmapPath.toString(), 0 /* flags: self targeting overlay */));
+ }
+
+ /**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* <p>The file descriptor is duplicated and the original may be closed by the application at any
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 22ef230..4589039 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -39,10 +39,17 @@
private final String mType;
/**
- * The request data.
+ * The full credential creation request data.
*/
@NonNull
- private final Bundle mData;
+ private final Bundle mCredentialData;
+
+ /**
+ * The partial request data that will be sent to the provider during the initial creation
+ * candidate query stage.
+ */
+ @NonNull
+ private final Bundle mCandidateQueryData;
/**
* Determines whether or not the request must only be fulfilled by a system provider.
@@ -58,18 +65,39 @@
}
/**
- * Returns the request data.
+ * Returns the full credential creation request data.
+ *
+ * For security reason, a provider will receive the request data in two stages. First it gets
+ * a partial request, {@link #getCandidateQueryData()} that do not contain sensitive user
+ * information; it uses this information to provide credential creation candidates that the
+ * [@code CredentialManager] will show to the user. Next, this full request data will be sent to
+ * a provider only if the user further grants the consent by choosing a candidate from the
+ * provider.
*/
@NonNull
- public Bundle getData() {
- return mData;
+ public Bundle getCredentialData() {
+ return mCredentialData;
+ }
+
+ /**
+ * Returns the partial request data that will be sent to the provider during the initial
+ * creation candidate query stage.
+ *
+ * For security reason, a provider will receive the request data in two stages. First it gets
+ * this partial request that do not contain sensitive user information; it uses this information
+ * to provide credential creation candidates that the [@code CredentialManager] will show to
+ * the user. Next, the full request data, {@link #getCredentialData()}, will be sent to a
+ * provider only if the user further grants the consent by choosing a candidate from the
+ * provider.
+ */
+ @NonNull
+ public Bundle getCandidateQueryData() {
+ return mCandidateQueryData;
}
/**
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
- *
- * @hide
*/
public boolean requireSystemProvider() {
return mRequireSystemProvider;
@@ -78,7 +106,8 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString8(mType);
- dest.writeBundle(mData);
+ dest.writeBundle(mCredentialData);
+ dest.writeBundle(mCandidateQueryData);
dest.writeBoolean(mRequireSystemProvider);
}
@@ -91,7 +120,8 @@
public String toString() {
return "CreateCredentialRequest {"
+ "type=" + mType
- + ", data=" + mData
+ + ", credentialData=" + mCredentialData
+ + ", candidateQueryData=" + mCandidateQueryData
+ ", requireSystemProvider=" + mRequireSystemProvider
+ "}";
}
@@ -100,44 +130,37 @@
* Constructs a {@link CreateCredentialRequest}.
*
* @param type the requested credential type
- * @param data the request data
- *
- * @throws IllegalArgumentException If type is empty
- */
- public CreateCredentialRequest(@NonNull String type, @NonNull Bundle data) {
- this(type, data, /*requireSystemProvider=*/ false);
- }
-
- /**
- * Constructs a {@link CreateCredentialRequest}.
- *
- * @param type the requested credential type
- * @param data the request data
- * @param requireSystemProvider whether or not the request must only be fulfilled by a system
- * provider
+ * @param credentialData the full credential creation request data
+ * @param candidateQueryData the partial request data that will be sent to the provider
+ * during the initial creation candidate query stage
+ * @param requireSystemProvider whether the request must only be fulfilled by a system provider
*
* @throws IllegalArgumentException If type is empty.
- *
- * @hide
*/
public CreateCredentialRequest(
@NonNull String type,
- @NonNull Bundle data,
+ @NonNull Bundle credentialData,
+ @NonNull Bundle candidateQueryData,
boolean requireSystemProvider) {
mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
- mData = requireNonNull(data, "data must not be null");
+ mCredentialData = requireNonNull(credentialData, "credentialData must not be null");
+ mCandidateQueryData = requireNonNull(candidateQueryData,
+ "candidateQueryData must not be null");
mRequireSystemProvider = requireSystemProvider;
}
private CreateCredentialRequest(@NonNull Parcel in) {
String type = in.readString8();
- Bundle data = in.readBundle();
+ Bundle credentialData = in.readBundle();
+ Bundle candidateQueryData = in.readBundle();
boolean requireSystemProvider = in.readBoolean();
mType = type;
AnnotationValidations.validate(NonNull.class, null, mType);
- mData = data;
- AnnotationValidations.validate(NonNull.class, null, mData);
+ mCredentialData = credentialData;
+ AnnotationValidations.validate(NonNull.class, null, mCredentialData);
+ mCandidateQueryData = candidateQueryData;
+ AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
mRequireSystemProvider = requireSystemProvider;
}
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index a0d3c0b..ed93dae 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -67,8 +67,6 @@
/**
* Returns true if the request must only be fulfilled by a system provider, and false
* otherwise.
- *
- * @hide
*/
public boolean requireSystemProvider() {
return mRequireSystemProvider;
@@ -100,24 +98,10 @@
*
* @param type the requested credential type
* @param data the request data
- *
- * @throws IllegalArgumentException If type is empty
- */
- public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
- this(type, data, /*requireSystemProvider=*/ false);
- }
-
- /**
- * Constructs a {@link GetCredentialOption}.
- *
- * @param type the requested credential type
- * @param data the request data
* @param requireSystemProvider whether or not the request must only be fulfilled by a system
* provider
*
* @throws IllegalArgumentException If type is empty.
- *
- * @hide
*/
public GetCredentialOption(
@NonNull String type,
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 1c4898a..18118f5 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,14 @@
package android.hardware;
+import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -80,6 +87,8 @@
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
+ private static native void nativeGetRuntimeSensors(
+ long nativeInstance, int deviceId, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@
private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
+ private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
+ private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
+ new SparseArray<>();
+
private boolean mDynamicSensorListDirty = true;
private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@
private HashMap<DynamicSensorCallback, Handler>
mDynamicSensorCallbacks = new HashMap<>();
private BroadcastReceiver mDynamicSensorBroadcastReceiver;
+ private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
@@ -121,6 +135,7 @@
private final boolean mIsPackageDebuggable;
private final Context mContext;
private final long mNativeInstance;
+ private final VirtualDeviceManager mVdm;
private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
@@ -139,6 +154,7 @@
mContext = context;
mNativeInstance = nativeCreate(context.getOpPackageName());
mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
+ mVdm = mContext.getSystemService(VirtualDeviceManager.class);
// initialize the sensor list
for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
+
+ }
+
+ /** @hide */
+ @Override
+ public List<Sensor> getSensorList(int type) {
+ final int deviceId = mContext.getDeviceId();
+ if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+ || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ return super.getSensorList(type);
+ }
+
+ // Cache the per-device lists on demand.
+ List<Sensor> list;
+ synchronized (mFullRuntimeSensorListByDevice) {
+ List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+ if (fullList == null) {
+ fullList = createRuntimeSensorListLocked(deviceId);
+ }
+ SparseArray<List<Sensor>> deviceSensorListByType =
+ mRuntimeSensorListByDeviceByType.get(deviceId);
+ list = deviceSensorListByType.get(type);
+ if (list == null) {
+ if (type == Sensor.TYPE_ALL) {
+ list = fullList;
+ } else {
+ list = new ArrayList<>();
+ for (Sensor i : fullList) {
+ if (i.getType() == type) {
+ list.add(i);
+ }
+ }
+ }
+ list = Collections.unmodifiableList(list);
+ deviceSensorListByType.append(type, list);
+ }
+ }
+ return list;
}
/** @hide */
@Override
protected List<Sensor> getFullSensorList() {
- return mFullSensorsList;
+ final int deviceId = mContext.getDeviceId();
+ if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
+ || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+ return mFullSensorsList;
+ }
+
+ List<Sensor> fullList;
+ synchronized (mFullRuntimeSensorListByDevice) {
+ fullList = mFullRuntimeSensorListByDevice.get(deviceId);
+ if (fullList == null) {
+ fullList = createRuntimeSensorListLocked(deviceId);
+ }
+ }
+ return fullList;
}
/** @hide */
@@ -446,12 +513,53 @@
}
}
+ private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
+ setupRuntimeSensorBroadcastReceiver();
+ List<Sensor> list = new ArrayList<>();
+ nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
+ mFullRuntimeSensorListByDevice.put(deviceId, list);
+ mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
+ for (Sensor s : list) {
+ mHandleToSensor.put(s.getHandle(), s);
+ }
+ return list;
+ }
+
+ private void setupRuntimeSensorBroadcastReceiver() {
+ if (mRuntimeSensorBroadcastReceiver == null) {
+ mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
+ synchronized (mFullRuntimeSensorListByDevice) {
+ final int deviceId = intent.getIntExtra(
+ EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
+ List<Sensor> removedSensors =
+ mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
+ if (removedSensors != null) {
+ for (Sensor s : removedSensors) {
+ cleanupSensorConnection(s);
+ }
+ }
+ mRuntimeSensorListByDeviceByType.remove(deviceId);
+ }
+ }
+ }
+ };
+
+ IntentFilter filter = new IntentFilter("virtual_device_removed");
+ filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
+ mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
+ Context.RECEIVER_NOT_EXPORTED);
+ }
+ }
+
private void setupDynamicSensorBroadcastReceiver() {
if (mDynamicSensorBroadcastReceiver == null) {
mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
+ if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
if (DEBUG_DYNAMIC_SENSOR) {
Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 9b07d3a..441fd88 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -137,7 +137,8 @@
VIRTUAL_DISPLAY_FLAG_TRUSTED,
VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP,
VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
- VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
+ VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
+ VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface VirtualDisplayFlag {}
@@ -403,6 +404,22 @@
*/
public static final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13;
+ /**
+ * Virtual display flags: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #createVirtualDisplay
+ * @see #VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @hide
+ */
+ @TestApi
+ public static final int VIRTUAL_DISPLAY_FLAG_OWN_FOCUS = 1 << 14;
+
+
/** @hide */
@IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index ade9fd6..b2dfd85 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.util.ArrayMap;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -41,14 +43,25 @@
public final class ProgramList implements AutoCloseable {
private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
new ArrayMap<>();
+ @GuardedBy("mLock")
private final List<ListCallback> mListCallbacks = new ArrayList<>();
+
+ @GuardedBy("mLock")
private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
+
+ @GuardedBy("mLock")
private OnCloseListener mOnCloseListener;
- private boolean mIsClosed = false;
- private boolean mIsComplete = false;
+
+ @GuardedBy("mLock")
+ private boolean mIsClosed;
+
+ @GuardedBy("mLock")
+ private boolean mIsComplete;
ProgramList() {}
@@ -227,6 +240,7 @@
}
}
+ @GuardedBy("mLock")
private void putLocked(RadioManager.ProgramInfo value,
List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
@@ -235,6 +249,7 @@
changedIdentifierList.add(sel);
}
+ @GuardedBy("mLock")
private void removeLocked(ProgramSelector.Identifier key,
List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 51236fe3..248b5d0 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -122,10 +122,10 @@
boolean isFunctionEnabled(String function);
/* Sets the current USB function. */
- void setCurrentFunctions(long functions);
+ void setCurrentFunctions(long functions, int operationId);
/* Compatibility version of setCurrentFunctions(long). */
- void setCurrentFunction(String function, boolean usbDataUnlocked);
+ void setCurrentFunction(String function, boolean usbDataUnlocked, int operationId);
/* Gets the current USB functions. */
long getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 342c336..7a8117c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -38,6 +38,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -52,6 +53,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* This class allows you to access the state of USB and communicate with USB devices.
@@ -95,7 +97,7 @@
* If the sticky intent has not been found, that indicates USB is disconnected,
* USB is not configued, MTP function is enabled, and all the other functions are disabled.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String ACTION_USB_STATE =
@@ -185,7 +187,7 @@
* <p>For more information about communicating with USB accessory handshake, refer to
* <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
*
- * {@hide}
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@SystemApi
@@ -197,7 +199,7 @@
* Boolean extra indicating whether USB is connected or disconnected.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_CONNECTED = "connected";
@@ -206,7 +208,7 @@
* Boolean extra indicating whether USB is connected or disconnected as host.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
public static final String USB_HOST_CONNECTED = "host_connected";
@@ -214,7 +216,7 @@
* Boolean extra indicating whether USB is configured.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_CONFIGURED = "configured";
@@ -225,7 +227,7 @@
* has explicitly asked for this data to be unlocked.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast.
*
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final String USB_DATA_UNLOCKED = "unlocked";
@@ -234,7 +236,7 @@
* A placeholder indicating that no USB function is being specified.
* Used for compatibility with old init scripts to indicate no functions vs. charging function.
*
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage
public static final String USB_FUNCTION_NONE = "none";
@@ -243,7 +245,7 @@
* Name of the adb USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_ADB = "adb";
@@ -251,7 +253,7 @@
* Name of the RNDIS ethernet USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_FUNCTION_RNDIS = "rndis";
@@ -260,7 +262,7 @@
* Name of the MTP USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_MTP = "mtp";
@@ -268,7 +270,7 @@
* Name of the PTP USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_PTP = "ptp";
@@ -276,7 +278,7 @@
* Name of the audio source USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
@@ -284,7 +286,7 @@
* Name of the MIDI USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_MIDI = "midi";
@@ -292,7 +294,7 @@
* Name of the Accessory USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
public static final String USB_FUNCTION_ACCESSORY = "accessory";
@@ -300,7 +302,7 @@
* Name of the NCM USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String USB_FUNCTION_NCM = "ncm";
@@ -308,32 +310,39 @@
/**
* Name of Gadget Hal Not Present;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_UNKNOWN = "unknown";
/**
* Name of the USB Gadget Hal Version v1.0;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_0 = "V1_0";
/**
* Name of the USB Gadget Hal Version v1.1;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_1 = "V1_1";
/**
* Name of the USB Gadget Hal Version v1.2;
*
- * {@hide}
+ * @hide
*/
public static final String GADGET_HAL_VERSION_1_2 = "V1_2";
/**
+ * Name of the USB Gadget Hal Version v2.0;
+ *
+ * @hide
+ */
+ public static final String GADGET_HAL_VERSION_2_0 = "V2_0";
+
+ /**
* Name of extra for {@link #ACTION_USB_PORT_CHANGED}
* containing the {@link UsbPort} object for the port.
*
@@ -369,7 +378,7 @@
* This is obtained with SystemClock.elapsedRealtime()
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_UEVENT_TIME =
@@ -383,7 +392,7 @@
* between communicating with USB accessory handshake, refer to
* <a href="https://source.android.com/devices/accessories/aoa">AOA</a> developer guide.</p>
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_STRING_COUNT =
@@ -393,7 +402,7 @@
* Boolean extra indicating whether got start accessory or not
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_START =
@@ -405,7 +414,7 @@
* sending {@link #ACTION_USB_ACCESSORY_HANDSHAKE}.
* Used in extras for {@link #ACTION_USB_ACCESSORY_HANDSHAKE} broadcasts.
*
- * {@hide}
+ * @hide
*/
@SystemApi
public static final String EXTRA_ACCESSORY_HANDSHAKE_END =
@@ -439,7 +448,7 @@
/**
* The Value for USB gadget hal is not presented.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_NOT_SUPPORTED = -1;
@@ -447,7 +456,7 @@
/**
* Value for Gadget Hal Version v1.0.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_0 = 10;
@@ -455,7 +464,7 @@
/**
* Value for Gadget Hal Version v1.1.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_1 = 11;
@@ -463,15 +472,23 @@
/**
* Value for Gadget Hal Version v1.2.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int GADGET_HAL_V1_2 = 12;
/**
+ * Value for Gadget Hal Version v2.0.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int GADGET_HAL_V2_0 = 20;
+
+ /**
* Value for USB_STATE is not configured.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_UNKNOWN = -1;
@@ -479,7 +496,7 @@
/**
* Value for USB Transfer Rate of Low Speed in Mbps (real value is 1.5Mbps).
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_LOW_SPEED = 2;
@@ -487,7 +504,7 @@
/**
* Value for USB Transfer Rate of Full Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_FULL_SPEED = 12;
@@ -495,7 +512,7 @@
/**
* Value for USB Transfer Rate of High Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_HIGH_SPEED = 480;
@@ -503,7 +520,7 @@
/**
* Value for USB Transfer Rate of Super Speed in Mbps.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_5G = 5 * 1024;
@@ -511,7 +528,7 @@
/**
* Value for USB Transfer Rate of 10G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_10G = 10 * 1024;
@@ -519,7 +536,7 @@
/**
* Value for USB Transfer Rate of 20G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_20G = 20 * 1024;
@@ -527,7 +544,7 @@
/**
* Value for USB Transfer Rate of 40G.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_DATA_TRANSFER_RATE_40G = 40 * 1024;
@@ -543,7 +560,7 @@
/**
* The Value for USB hal is not presented.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_NOT_SUPPORTED = -1;
@@ -551,7 +568,7 @@
/**
* Value for USB Hal Version v1.0.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_0 = 10;
@@ -559,7 +576,7 @@
/**
* Value for USB Hal Version v1.1.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_1 = 11;
@@ -567,7 +584,7 @@
/**
* Value for USB Hal Version v1.2.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_2 = 12;
@@ -575,7 +592,7 @@
/**
* Value for USB Hal Version v1.3.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final int USB_HAL_V1_3 = 13;
@@ -590,63 +607,63 @@
/**
* Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_NONE = 0;
/**
* Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_MTP = GadgetFunction.MTP;
/**
* Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_PTP = GadgetFunction.PTP;
/**
* Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
/**
* Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
/**
* Code for the accessory usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
/**
* Code for the audio source usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
/**
* Code for the adb usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_ADB = GadgetFunction.ADB;
/**
* Code for the ncm source usb function.
- * {@hide}
+ * @hide
*/
@SystemApi
public static final long FUNCTION_NCM = 1 << 10;
@@ -656,6 +673,11 @@
private static final Map<String, Long> FUNCTION_NAME_TO_CODE = new HashMap<>();
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
static {
FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_MTP, FUNCTION_MTP);
FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_PTP, FUNCTION_PTP);
@@ -687,6 +709,7 @@
GADGET_HAL_V1_0,
GADGET_HAL_V1_1,
GADGET_HAL_V1_2,
+ GADGET_HAL_V2_0,
})
public @interface UsbGadgetHalVersion {}
@@ -705,7 +728,7 @@
private final IUsbManager mService;
/**
- * {@hide}
+ * @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public UsbManager(Context context, IUsbManager service) {
@@ -816,7 +839,7 @@
* {@link #FUNCTION_PTP} are supported.
* @return A ParcelFileDescriptor holding the valid fd, or null if the fd was not found.
*
- * {@hide}
+ * @hide
*/
public ParcelFileDescriptor getControlFd(long function) {
try {
@@ -977,7 +1000,7 @@
* Only system components can call this function.
* @param device to request permissions for
*
- * {@hide}
+ * @hide
*/
public void grantPermission(UsbDevice device) {
grantPermission(device, Process.myUid());
@@ -989,7 +1012,7 @@
* @param device to request permissions for
* @uid uid to give permission
*
- * {@hide}
+ * @hide
*/
public void grantPermission(UsbDevice device, int uid) {
try {
@@ -1005,7 +1028,7 @@
* @param device to request permissions for
* @param packageName of package to grant permissions
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1030,7 +1053,7 @@
* @param function name of the USB function
* @return true if the USB function is enabled
*
- * {@hide}
+ * @hide
*/
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1062,14 +1085,17 @@
* @param functions the USB function(s) to set, as a bitwise mask.
* Must satisfy {@link UsbManager#areSettableFunctions}
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
public void setCurrentFunctions(@UsbFunctionMode long functions) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
- mService.setCurrentFunctions(functions);
+ mService.setCurrentFunctions(functions, operationId);
} catch (RemoteException e) {
+ Log.e(TAG, "setCurrentFunctions: failed to call setCurrentFunctions. functions:"
+ + functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
@@ -1081,14 +1107,17 @@
* @param functions the USB function(s) to set.
* @param usbDataUnlocked unused
- * {@hide}
+ * @hide
*/
@Deprecated
@UnsupportedAppUsage
public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
- mService.setCurrentFunction(functions, usbDataUnlocked);
+ mService.setCurrentFunction(functions, usbDataUnlocked, operationId);
} catch (RemoteException e) {
+ Log.e(TAG, "setCurrentFunction: failed to call setCurrentFunction. functions:"
+ + functions + ", opId:" + operationId, e);
throw e.rethrowFromSystemServer();
}
}
@@ -1103,7 +1132,7 @@
* @return The currently enabled functions, in a bitwise mask.
* A zero mask indicates that the current function is the charging function.
*
- * {@hide}
+ * @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1129,7 +1158,7 @@
* @param functions functions to set, in a bitwise mask.
* Must satisfy {@link UsbManager#areSettableFunctions}
*
- * {@hide}
+ * @hide
*/
public void setScreenUnlockedFunctions(long functions) {
try {
@@ -1145,7 +1174,7 @@
* @return The currently set screen enabled functions.
* A zero mask indicates that the screen unlocked functions feature is not enabled.
*
- * {@hide}
+ * @hide
*/
public long getScreenUnlockedFunctions() {
try {
@@ -1167,19 +1196,17 @@
*
* @return The value of currently USB Bandwidth.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
public int getUsbBandwidthMbps() {
int usbSpeed;
-
try {
usbSpeed = mService.getCurrentUsbSpeed();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
-
return usbSpeedToBandwidth(usbSpeed);
}
@@ -1191,7 +1218,7 @@
*
* @return a integer {@code GADGET_HAL_*} represent hal version.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1211,7 +1238,7 @@
*
* @return a integer {@code USB_HAL_*} represent hal version.
*
- * {@hide}
+ * @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@RequiresPermission(Manifest.permission.MANAGE_USB)
@@ -1507,7 +1534,7 @@
* @param usbDeviceConnectionHandler The component to handle usb connections,
* {@code null} to unset.
*
- * {@hide}
+ * @hide
*/
public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
try {
@@ -1526,7 +1553,7 @@
*
* @return Whether the mask is settable.
*
- * {@hide}
+ * @hide
*/
public static boolean areSettableFunctions(long functions) {
return functions == FUNCTION_NONE
@@ -1540,7 +1567,7 @@
*
* @return String representation of given mask
*
- * {@hide}
+ * @hide
*/
public static String usbFunctionsToString(long functions) {
StringJoiner joiner = new StringJoiner(",");
@@ -1576,7 +1603,7 @@
*
* @return A mask of all valid functions in the string
*
- * {@hide}
+ * @hide
*/
public static long usbFunctionsFromString(String functions) {
if (functions == null || functions.equals(USB_FUNCTION_NONE)) {
@@ -1598,7 +1625,7 @@
*
* @return a value of USB bandwidth
*
- * {@hide}
+ * @hide
*/
public static int usbSpeedToBandwidth(int speed) {
switch (speed) {
@@ -1632,12 +1659,14 @@
*
* @return String representation of Usb Gadget Hal Version
*
- * {@hide}
+ * @hide
*/
public static @NonNull String usbGadgetHalVersionToString(int version) {
String halVersion;
- if (version == GADGET_HAL_V1_2) {
+ if (version == GADGET_HAL_V2_0) {
+ halVersion = GADGET_HAL_VERSION_2_0;
+ } else if (version == GADGET_HAL_V1_2) {
halVersion = GADGET_HAL_VERSION_1_2;
} else if (version == GADGET_HAL_V1_1) {
halVersion = GADGET_HAL_VERSION_1_1;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index fb66cb9..872414a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -70,11 +70,14 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.graphics.Region;
@@ -98,6 +101,7 @@
import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
+import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver;
import android.view.Choreographer;
@@ -158,6 +162,8 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.util.RingBuffer;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -730,7 +736,6 @@
@Override
public final void initializeInternal(@NonNull IInputMethod.InitParams params) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initializeInternal");
- mConfigTracker.onInitialize(params.configChanges);
mPrivOps.set(params.privilegedOperations);
InputMethodPrivilegedOperationsRegistry.put(params.token, mPrivOps);
mNavigationBarController.onNavButtonFlagsChanged(params.navigationBarFlags);
@@ -1601,6 +1606,8 @@
mHideNavBarForKeyboard = getApplicationContext().getResources().getBoolean(
com.android.internal.R.bool.config_hideNavBarForKeyboard);
+ initConfigurationTracker();
+
// TODO(b/111364446) Need to address context lifecycle issue if need to re-create
// for update resources & configuration correctly when show soft input
// in non-default display.
@@ -1656,6 +1663,36 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
+ private void initConfigurationTracker() {
+ final int flags = PackageManager.GET_META_DATA
+ | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ final ComponentName imeComponent = new ComponentName(
+ getPackageName(), getClass().getName());
+ final String imeId = imeComponent.flattenToShortString();
+ final ServiceInfo si;
+ try {
+ si = getPackageManager().getServiceInfo(imeComponent,
+ PackageManager.ComponentInfoFlags.of(flags));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.wtf(TAG, "Unable to find input method " + imeId, e);
+ return;
+ }
+ try (XmlResourceParser parser = si.loadXmlMetaData(getPackageManager(),
+ InputMethod.SERVICE_META_DATA);
+ TypedArray sa = getResources().obtainAttributes(Xml.asAttributeSet(parser),
+ com.android.internal.R.styleable.InputMethod)) {
+ if (parser == null) {
+ throw new XmlPullParserException(
+ "No " + InputMethod.SERVICE_META_DATA + " meta-data");
+ }
+ final int handledConfigChanges = sa.getInt(
+ com.android.internal.R.styleable.InputMethod_configChanges, 0);
+ mConfigTracker.onInitialize(handledConfigChanges);
+ } catch (Exception e) {
+ Log.wtf(TAG, "Unable to load input method " + imeId, e);
+ }
+ }
+
/**
* This is a hook that subclasses can use to perform initialization of
* their interface. It is called for you prior to any of your UI objects
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index a887f2a..d31540a 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -58,6 +58,8 @@
void setUserIcon(int userId, in Bitmap icon);
ParcelFileDescriptor getUserIcon(int userId);
UserInfo getPrimaryUser();
+ int getMainUserId();
+ int getPreviousFullUserToEnterForeground();
List<UserInfo> getUsers(boolean excludePartial, boolean excludeDying, boolean excludePreCreated);
List<UserInfo> getProfiles(int userId, boolean enabledOnly);
int[] getProfileIds(int userId, boolean enabledOnly);
diff --git a/core/java/android/os/ServiceManager.java b/core/java/android/os/ServiceManager.java
index 9ea4278..394927e 100644
--- a/core/java/android/os/ServiceManager.java
+++ b/core/java/android/os/ServiceManager.java
@@ -252,10 +252,12 @@
}
/**
- * Returns the list of declared instances for an interface.
+ * Returns an array of all declared instances for a particular interface.
*
- * @return true if the service is declared somewhere (eg. VINTF manifest) and
- * waitForService should always be able to return the service.
+ * For instance, if 'android.foo.IFoo/foo' is declared (e.g. in VINTF
+ * manifest), and 'android.foo.IFoo' is passed here, then ["foo"] would be
+ * returned.
+ *
* @hide
*/
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index fb197f5..b919393 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -241,9 +241,16 @@
/**
* Writes a trace message to indicate that a given section of code has
* begun. Must be followed by a call to {@link #asyncTraceEnd} using the same
- * tag. Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
- * asynchronous events do not need to be nested. The name and cookie used to
- * begin an event must be used to end it.
+ * tag, name and cookie.
+ *
+ * If two events with the same methodName overlap in time then they *must* have
+ * different cookie values. If they do not, the trace can become corrupted
+ * in unpredictable ways.
+ *
+ * Unlike {@link #traceBegin(long, String)} and {@link #traceEnd(long)},
+ * asynchronous events cannot be not nested. Consider using
+ * {@link #asyncTraceForTrackBegin(long, String, String, int)}
+ * if nested asynchronous events are needed.
*
* @param traceTag The trace tag.
* @param methodName The method name to appear in the trace.
@@ -264,6 +271,9 @@
* Must be called exactly once for each call to {@link #asyncTraceBegin(long, String, int)}
* using the same tag, name and cookie.
*
+ * See the documentation for {@link #asyncTraceBegin(long, String, int)}.
+ * for inteded usage of this method.
+ *
* @param traceTag The trace tag.
* @param methodName The method name to appear in the trace.
* @param cookie Unique identifier for distinguishing simultaneous events
@@ -283,14 +293,73 @@
* Writes a trace message to indicate that a given section of code has
* begun. Must be followed by a call to {@link #asyncTraceForTrackEnd} using the same
* track name and cookie.
- * This function operates exactly like {@link #asyncTraceBegin(long, String, int)},
- * except with the inclusion of a track name argument for where this method should appear.
- * The cookie must be unique on the trackName level, not the methodName level
+ *
+ * Events with the same trackName and cookie nest inside each other in the
+ * same way as calls to {@link #traceBegin(long, String)} and
+ * {@link #traceEnd(long)}.
+ *
+ * If two events with the same trackName overlap in time but do not nest
+ * correctly, then they *must* have different cookie values. If they do not,
+ * the trace can become corrupted in unpredictable ways.
+ *
+ * Good Example:
+ *
+ * public void parent() {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "parent", mId);
+ * child()
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+ * }
+ *
+ * public void child() {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "Track", "child", mId);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "Track", mId);
+ * }
+ *
+ * This would be visualized as so:
+ * [ Parent ]
+ * [ Child ]
+ *
+ * Bad Example:
+ *
+ * public static void processData(String dataToProcess) {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", 0);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", 0);
+ * }
+ *
+ * public static void processDataInParallel({@code List<String>} data) {
+ * ExecutorService executor = Executors.newCachedThreadPool();
+ * for (String s : data) {
+ * pool.execute(() -> processData(s));
+ * }
+ * }
+ *
+ * This is invalid because it's possible for processData to be run many times
+ * in parallel (i.e. processData events overlap) but the same cookie is
+ * provided each time.
+ *
+ * To fix this, specify a different id in each invocation of processData:
+ *
+ * public static void processData(String dataToProcess, int id) {
+ * asyncTraceForTrackBegin(TRACE_TAG_ALWAYS, "processDataInParallel", "processData", id);
+ * // Some code here.
+ * asyncTraceForTrackEnd(TRACE_TAG_ALWAYS, "processDataInParallel", id);
+ * }
+ *
+ * public static void processDataInParallel({@code List<String>} data) {
+ * ExecutorService executor = Executors.newCachedThreadPool();
+ * for (int i = 0; i < data.size(); ++i) {
+ * pool.execute(() -> processData(data.get(i), i));
+ * }
+ * }
*
* @param traceTag The trace tag.
* @param trackName The track where the event should appear in the trace.
* @param methodName The method name to appear in the trace.
- * @param cookie Unique identifier for distinguishing simultaneous events
+ * @param cookie Unique identifier used for nesting events on a single
+ * track. Events which overlap without nesting on the same
+ * track must have different values for cookie.
*
* @hide
*/
@@ -307,9 +376,14 @@
* {@link #asyncTraceForTrackBegin(long, String, String, int)}
* using the same tag, track name, and cookie.
*
+ * See the documentation for {@link #asyncTraceForTrackBegin(long, String, String, int)}.
+ * for inteded usage of this method.
+ *
* @param traceTag The trace tag.
* @param trackName The track where the event should appear in the trace.
- * @param cookie Unique identifier for distinguishing simultaneous events
+ * @param cookie Unique identifier used for nesting events on a single
+ * track. Events which overlap without nesting on the same
+ * track must have different values for cookie.
*
* @hide
*/
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 1f21bfe..dd02e02 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2377,14 +2377,16 @@
}
/**
- * Returns true if the context user is the designated "main user" of the device. This user may
- * have access to certain features which are limited to at most one user.
+ * Returns {@code true} if the context user is the designated "main user" of the device. This
+ * user may have access to certain features which are limited to at most one user. There will
+ * never be more than one main user on a device.
*
- * <p>Currently, the first human user on the device will be the main user; in the future, the
- * concept may be transferable, so a different user (or even no user at all) may be designated
- * the main user instead.
+ * <p>Currently, on most form factors the first human user on the device will be the main user;
+ * in the future, the concept may be transferable, so a different user (or even no user at all)
+ * may be designated the main user instead. On other form factors there might not be a main
+ * user.
*
- * <p>Note that this will be the not be the system user on devices for which
+ * <p>Note that this will not be the system user on devices for which
* {@link #isHeadlessSystemUserMode()} returns true.
* @hide
*/
@@ -2400,6 +2402,29 @@
}
/**
+ * Returns the designated "main user" of the device, or {@code null} if there is no main user.
+ *
+ * @see #isMainUser()
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ Manifest.permission.MANAGE_USERS,
+ Manifest.permission.CREATE_USERS,
+ Manifest.permission.QUERY_USERS})
+ public @Nullable UserHandle getMainUser() {
+ try {
+ final int mainUserId = mService.getMainUserId();
+ if (mainUserId == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(mainUserId);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Used to check if the context user is an admin user. An admin user is allowed to
* modify or configure certain settings that aren't available to non-admin users,
* create and delete additional users, etc. There can be more than one admin users.
@@ -2951,8 +2976,15 @@
* </ol>
*
* @return whether the user is visible at the moment, as defined above.
+ *
+ * @hide
*/
+ @SystemApi
@UserHandleAware
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
public boolean isUserVisible() {
try {
return mService.isUserVisible(mUserId);
@@ -2965,9 +2997,14 @@
* Gets the visible users (as defined by {@link #isUserVisible()}.
*
* @return visible users at the moment.
+ *
+ * @hide
*/
- @RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
- Manifest.permission.INTERACT_ACROSS_USERS})
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.MANAGE_USERS"
+ })
public @NonNull Set<UserHandle> getVisibleUsers() {
ArraySet<UserHandle> result = new ArraySet<>();
try {
@@ -4275,6 +4312,43 @@
}
/**
+ * Returns the user who was last in the foreground, not including the current user and not
+ * including profiles.
+ *
+ * <p>Returns {@code null} if there is no previous user, for example if there
+ * is only one full user (i.e. only one user which is not a profile) on the device.
+ *
+ * <p>This method may be used for example to find the user to switch back to if the
+ * current user is removed, or if creating a new user is aborted.
+ *
+ * <p>Note that reboots do not interrupt this calculation; the previous user need not have
+ * used the device since it rebooted.
+ *
+ * <p>Note also that on devices that support multiple users on multiple displays, it is possible
+ * that the returned user will be visible on a secondary display, as the foreground user is the
+ * one associated with the main display.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS,
+ android.Manifest.permission.QUERY_USERS
+ })
+ public @Nullable UserHandle getPreviousForegroundUser() {
+ try {
+ final int previousUser = mService.getPreviousFullUserToEnterForeground();
+ if (previousUser == UserHandle.USER_NULL) {
+ return null;
+ }
+ return UserHandle.of(previousUser);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Checks whether it's possible to add more users.
*
* @return true if more users can be added, false if limit has been reached.
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 71bc4b3..3448a9e 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -227,6 +227,31 @@
}
/**
+ * Computes a legacy vibration pattern (i.e. a pattern with duration values for "off/on"
+ * vibration components) that is equivalent to this VibrationEffect.
+ *
+ * <p>All non-repeating effects created with {@link #createWaveform(int[], int)} are convertible
+ * into an equivalent vibration pattern with this method. It is not guaranteed that an effect
+ * created with other means becomes converted into an equivalent legacy vibration pattern, even
+ * if it has an equivalent vibration pattern. If this method is unable to create an equivalent
+ * vibration pattern for such effects, it will return {@code null}.
+ *
+ * <p>Note that a valid equivalent long[] pattern cannot be created for an effect that has any
+ * form of repeating behavior, regardless of how the effect was created. For repeating effects,
+ * the method will always return {@code null}.
+ *
+ * @return a long array representing a vibration pattern equivalent to the VibrationEffect, if
+ * the method successfully derived a vibration pattern equivalent to the effect
+ * (this will always be the case if the effect was created via
+ * {@link #createWaveform(int[], int)} and is non-repeating). Otherwise, returns
+ * {@code null}.
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ public abstract long[] computeCreateWaveformOffOnTimingsOrNull();
+
+ /**
* Create a waveform vibration.
*
* <p>Waveform vibrations are a potentially repeating series of timing and amplitude pairs,
@@ -641,6 +666,51 @@
return mRepeatIndex;
}
+ /** @hide */
+ @Override
+ @Nullable
+ public long[] computeCreateWaveformOffOnTimingsOrNull() {
+ if (getRepeatIndex() >= 0) {
+ // Repeating effects cannot be fully represented as a long[] legacy pattern.
+ return null;
+ }
+
+ List<VibrationEffectSegment> segments = getSegments();
+
+ // The maximum possible size of the final pattern is 1 plus the number of segments in
+ // the original effect. This is because we will add an empty "off" segment at the
+ // start of the pattern if the first segment of the original effect is an "on" segment.
+ // (because the legacy patterns start with an "off" pattern). Other than this one case,
+ // we will add the durations of back-to-back segments of similar amplitudes (amplitudes
+ // that are all "on" or "off") and create a pattern entry for the total duration, which
+ // will not take more number pattern entries than the number of segments processed.
+ long[] patternBuffer = new long[segments.size() + 1];
+ int patternIndex = 0;
+
+ for (int i = 0; i < segments.size(); i++) {
+ StepSegment stepSegment =
+ castToValidStepSegmentForOffOnTimingsOrNull(segments.get(i));
+ if (stepSegment == null) {
+ // This means that there is 1 or more segments of this effect that is/are not a
+ // possible component of a legacy vibration pattern. Thus, the VibrationEffect
+ // does not have any equivalent legacy vibration pattern.
+ return null;
+ }
+
+ boolean isSegmentOff = stepSegment.getAmplitude() == 0;
+ // Even pattern indices are "off", and odd pattern indices are "on"
+ boolean isCurrentPatternIndexOff = (patternIndex % 2) == 0;
+ if (isSegmentOff != isCurrentPatternIndexOff) {
+ // Move the pattern index one step ahead, so that the current segment's
+ // "off"/"on" property matches that of the index's
+ ++patternIndex;
+ }
+ patternBuffer[patternIndex] += stepSegment.getDuration();
+ }
+
+ return Arrays.copyOf(patternBuffer, patternIndex + 1);
+ }
+
/** @hide */
@Override
public void validate() {
@@ -806,6 +876,31 @@
return new Composed[size];
}
};
+
+ /**
+ * Casts a provided {@link VibrationEffectSegment} to a {@link StepSegment} and returns it,
+ * only if it can possibly be a segment for an effect created via
+ * {@link #createWaveform(int[], int)}. Otherwise, returns {@code null}.
+ */
+ @Nullable
+ private static StepSegment castToValidStepSegmentForOffOnTimingsOrNull(
+ VibrationEffectSegment segment) {
+ if (!(segment instanceof StepSegment)) {
+ return null;
+ }
+
+ StepSegment stepSegment = (StepSegment) segment;
+ if (stepSegment.getFrequencyHz() != 0) {
+ return null;
+ }
+
+ float amplitude = stepSegment.getAmplitude();
+ if (amplitude != 0 && amplitude != DEFAULT_AMPLITUDE) {
+ return null;
+ }
+
+ return stepSegment;
+ }
}
/**
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index aebd91a..84a233f 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2183,6 +2183,17 @@
}
}
+ /**
+ * Returns a Looper which messages such as {@link WallpaperService#DO_ATTACH},
+ * {@link WallpaperService#DO_DETACH} etc. are sent to.
+ * By default, returns the process's main looper.
+ * @hide
+ */
+ @NonNull
+ public Looper onProvideEngineLooper() {
+ return super.getMainLooper();
+ }
+
private boolean isValid(RectF area) {
if (area == null) return false;
boolean valid = area.bottom > area.top && area.left < area.right
@@ -2215,12 +2226,12 @@
Engine mEngine;
@SetWallpaperFlags int mWhich;
- IWallpaperEngineWrapper(WallpaperService context,
+ IWallpaperEngineWrapper(WallpaperService service,
IWallpaperConnection conn, IBinder windowToken,
int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
int displayId, @SetWallpaperFlags int which) {
mWallpaperManager = getSystemService(WallpaperManager.class);
- mCaller = new HandlerCaller(context, context.getMainLooper(), this, true);
+ mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
mConnection = conn;
mWindowToken = windowToken;
mWindowType = windowType;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index e5c9adb..dded76c 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -26,7 +26,6 @@
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
-import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
@@ -726,7 +725,7 @@
*/
@Deprecated
@RequiresPermission(value = android.Manifest.permission.READ_PHONE_STATE, conditional = true)
- public void onCallStateChanged(@CallState int state, String phoneNumber) {
+ public void onCallStateChanged(@Annotation.CallState int state, String phoneNumber) {
// default implementation empty
}
@@ -1569,12 +1568,48 @@
() -> mExecutor.execute(() -> psl.onRadioPowerStateChanged(state)));
}
- public void onCallAttributesChanged(CallAttributes callAttributes) {
+ public void onCallStatesChanged(List<CallState> callStateList) {
PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
if (psl == null) return;
+ if (callStateList == null) return;
+ CallAttributes ca;
+ if (callStateList.isEmpty()) {
+ ca = new CallAttributes(
+ new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ } else {
+ int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ for (CallState cs : callStateList) {
+ switch (cs.getCallClassification()) {
+ case CallState.CALL_CLASSIFICATION_FOREGROUND:
+ foregroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_BACKGROUND:
+ backgroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_RINGING:
+ ringingCallState = cs.getCallState();
+ break;
+ default:
+ break;
+ }
+ }
+ ca = new CallAttributes(
+ new PreciseCallState(
+ ringingCallState, foregroundCallState, backgroundCallState,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ callStateList.get(0).getNetworkType(),
+ callStateList.get(0).getCallQuality());
+ }
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> psl.onCallAttributesChanged(callAttributes)));
+ () -> mExecutor.execute(
+ () -> psl.onCallAttributesChanged(ca)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e8960b8..257f3b7 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,7 @@
import android.os.Build;
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -62,7 +63,7 @@
* appropriate sub-interfaces.
*/
public class TelephonyCallback {
-
+ private static final String LOG_TAG = "TelephonyCallback";
/**
* Experiment flag to set the per-pid registration limit for TelephonyCallback
*
@@ -1332,7 +1333,9 @@
@SystemApi
public interface CallAttributesListener {
/**
- * Callback invoked when the call attributes changes on the registered subscription.
+ * Callback invoked when the call attributes changes on the active call on the registered
+ * subscription. If the user swaps between a foreground and background call the call
+ * attributes will be reported for the active call only.
* Note, the registration subscription ID comes from {@link TelephonyManager} object
* which registers TelephonyCallback by
* {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
@@ -1346,9 +1349,77 @@
* {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
*
* @param callAttributes the call attributes
+ * @deprecated Use onCallStatesChanged({@link List<CallState>}) to get each of call
+ * state for all ongoing calls on the subscription.
*/
@RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
- void onCallAttributesChanged(@NonNull CallAttributes callAttributes);
+ @Deprecated
+ default void onCallAttributesChanged(@NonNull CallAttributes callAttributes) {
+ Log.w(LOG_TAG, "onCallAttributesChanged(List<CallState>) should be "
+ + "overridden.");
+ }
+
+ /**
+ * Callback invoked when the call attributes changes on the ongoing calls on the registered
+ * subscription. If there are 1 foreground and 1 background call, Two {@link CallState}
+ * will be passed.
+ * Note, the registration subscription ID comes from {@link TelephonyManager} object
+ * which registers TelephonyCallback by
+ * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subscription ID. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ * In the event that there are no active(state is not
+ * {@link PreciseCallState#PRECISE_CALL_STATE_IDLE}) calls, this API will report empty list.
+ *
+ * The calling app should have carrier privileges
+ * (see {@link TelephonyManager#hasCarrierPrivileges}) if it does not have the
+ * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}.
+ *
+ * @param callStateList the list of call states for each ongoing call. If there are
+ * a active call and a holding call, 1 call attributes for
+ * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} and another
+ * for {@link PreciseCallState#PRECISE_CALL_STATE_HOLDING}
+ * will be in this list.
+ */
+ // Added as default for backward compatibility
+ @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+ default void onCallStatesChanged(@NonNull List<CallState> callStateList) {
+ if (callStateList.size() > 0) {
+ int foregroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int backgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ int ringingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ for (CallState cs : callStateList) {
+ switch (cs.getCallClassification()) {
+ case CallState.CALL_CLASSIFICATION_FOREGROUND:
+ foregroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_BACKGROUND:
+ backgroundCallState = cs.getCallState();
+ break;
+ case CallState.CALL_CLASSIFICATION_RINGING:
+ ringingCallState = cs.getCallState();
+ break;
+ default:
+ break;
+ }
+ }
+ onCallAttributesChanged(new CallAttributes(
+ new PreciseCallState(
+ ringingCallState, foregroundCallState, backgroundCallState,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ callStateList.get(0).getNetworkType(),
+ callStateList.get(0).getCallQuality()));
+ } else {
+ onCallAttributesChanged(new CallAttributes(
+ new PreciseCallState(PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ PreciseCallState.PRECISE_CALL_STATE_IDLE,
+ DisconnectCause.NOT_VALID, PreciseDisconnectCause.NOT_VALID),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()));
+ }
+ }
}
/**
@@ -1702,14 +1773,13 @@
() -> mExecutor.execute(() -> listener.onRadioPowerStateChanged(state)));
}
- public void onCallAttributesChanged(CallAttributes callAttributes) {
+ public void onCallStatesChanged(List<CallState> callStateList) {
CallAttributesListener listener =
(CallAttributesListener) mTelephonyCallbackWeakRef.get();
if (listener == null) return;
Binder.withCleanCallingIdentity(
- () -> mExecutor.execute(() -> listener.onCallAttributesChanged(
- callAttributes)));
+ () -> mExecutor.execute(() -> listener.onCallStatesChanged(callStateList)));
}
public void onActiveDataSubIdChanged(int subId) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index a3696e3..0a1538de 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -32,13 +32,13 @@
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.NetworkType;
-import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.PreciseDisconnectCauses;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
import android.telephony.Annotation.SrvccState;
import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.util.ArraySet;
import android.util.Log;
@@ -741,17 +741,20 @@
* @param slotIndex for which precise call state changed. Can be derived from subId except when
* subId is invalid.
* @param subId for which precise call state changed.
- * @param ringCallPreciseState ringCall state.
- * @param foregroundCallPreciseState foreground call state.
- * @param backgroundCallPreciseState background call state.
+ * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+ * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId} for
+ * ringing, foreground & background calls.
+ * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+ * background calls.
+ * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
*/
public void notifyPreciseCallState(int slotIndex, int subId,
- @PreciseCallStates int ringCallPreciseState,
- @PreciseCallStates int foregroundCallPreciseState,
- @PreciseCallStates int backgroundCallPreciseState) {
+ @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+ @Annotation.ImsCallServiceType int[] imsServiceTypes,
+ @Annotation.ImsCallType int[] imsCallTypes) {
try {
- sRegistry.notifyPreciseCallState(slotIndex, subId, ringCallPreciseState,
- foregroundCallPreciseState, backgroundCallPreciseState);
+ sRegistry.notifyPreciseCallState(slotIndex, subId, callStates,
+ imsCallIds, imsServiceTypes, imsCallTypes);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 608cbda..4277d01 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -140,6 +140,13 @@
public static final String SETTINGS_ADB_METRICS_WRITER = "settings_adb_metrics_writer";
/**
+ * Flag to show stylus-specific preferences in Connected Devices
+ * @hide
+ */
+ public static final String SETTINGS_SHOW_STYLUS_PREFERENCES =
+ "settings_show_stylus_preferences";
+
+ /**
* Flag to enable/disable biometrics enrollment v2
* @hide
*/
@@ -181,10 +188,12 @@
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
+ DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
+
static {
PERSISTENT_FLAGS = new HashSet<>();
PERSISTENT_FLAGS.add(SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE);
diff --git a/core/java/android/util/IntArray.java b/core/java/android/util/IntArray.java
index 7b28b8a..bc0e35d 100644
--- a/core/java/android/util/IntArray.java
+++ b/core/java/android/util/IntArray.java
@@ -234,4 +234,23 @@
public int[] toArray() {
return Arrays.copyOf(mValues, mSize);
}
+
+ @Override
+ public String toString() {
+ // Code below is copied from Arrays.toString(), but uses mSize in the lopp (it cannot call
+ // Arrays.toString() directly as it would return the unused elements as well)
+ int iMax = mSize - 1;
+ if (iMax == -1) {
+ return "[]";
+ }
+ StringBuilder b = new StringBuilder();
+ b.append('[');
+ for (int i = 0;; i++) {
+ b.append(mValues[i]);
+ if (i == iMax) {
+ return b.append(']').toString();
+ }
+ b.append(", ");
+ }
+ }
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 5933ae4..2745858 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -319,6 +319,19 @@
public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 10;
/**
+ * Flag: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #FLAG_TRUSTED
+ * @hide
+ */
+ public static final int FLAG_OWN_FOCUS = 1 << 11;
+
+ /**
* Display flag: Indicates that the contents of the display should not be scaled
* to fit the physical screen dimensions. Used for development only to emulate
* devices with smaller physicals screens while preserving density.
@@ -996,6 +1009,28 @@
}
/**
+ * Returns the {@link DisplayShape} which is based on display coordinates.
+ *
+ * To get the {@link DisplayShape} based on the window frame, use
+ * {@link WindowInsets#getDisplayShape()} instead.
+ *
+ * @see DisplayShape
+ */
+ @SuppressLint("VisiblySynchronized")
+ @NonNull
+ public DisplayShape getShape() {
+ synchronized (mLock) {
+ updateDisplayInfoLocked();
+ final DisplayShape displayShape = mDisplayInfo.displayShape;
+ final @Surface.Rotation int rotation = getLocalRotation();
+ if (displayShape != null && rotation != mDisplayInfo.rotation) {
+ return displayShape.setRotation(rotation);
+ }
+ return displayShape;
+ }
+ }
+
+ /**
* Gets the pixel format of the display.
* @return One of the constants defined in {@link android.graphics.PixelFormat}.
*
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 0ba3072..138017c 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -323,6 +323,9 @@
@Surface.Rotation
public int installOrientation;
+ @Nullable
+ public DisplayShape displayShape;
+
public static final @android.annotation.NonNull Creator<DisplayInfo> CREATOR = new Creator<DisplayInfo>() {
@Override
public DisplayInfo createFromParcel(Parcel source) {
@@ -395,7 +398,8 @@
&& brightnessMaximum == other.brightnessMaximum
&& brightnessDefault == other.brightnessDefault
&& Objects.equals(roundedCorners, other.roundedCorners)
- && installOrientation == other.installOrientation;
+ && installOrientation == other.installOrientation
+ && Objects.equals(displayShape, other.displayShape);
}
@Override
@@ -448,6 +452,7 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
public void readFromParcel(Parcel source) {
@@ -506,6 +511,7 @@
userDisabledHdrTypes[i] = source.readInt();
}
installOrientation = source.readInt();
+ displayShape = source.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -562,6 +568,7 @@
dest.writeInt(userDisabledHdrTypes[i]);
}
dest.writeInt(installOrientation);
+ dest.writeTypedObject(displayShape, flags);
}
@Override
diff --git a/core/java/android/view/DisplayShape.aidl b/core/java/android/view/DisplayShape.aidl
new file mode 100644
index 0000000..af8b417
--- /dev/null
+++ b/core/java/android/view/DisplayShape.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+parcelable DisplayShape;
diff --git a/core/java/android/view/DisplayShape.java b/core/java/android/view/DisplayShape.java
new file mode 100644
index 0000000..43bd773
--- /dev/null
+++ b/core/java/android/view/DisplayShape.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_0;
+
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.DisplayUtils;
+import android.util.PathParser;
+import android.util.RotationUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+/**
+ * A class representing the shape of a display. It provides a {@link Path} of the display shape of
+ * the display shape.
+ *
+ * {@link DisplayShape} is immutable.
+ */
+public final class DisplayShape implements Parcelable {
+
+ /** @hide */
+ public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */,
+ 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */,
+ 0 /* rotation */);
+
+ /** @hide */
+ @VisibleForTesting
+ public final String mDisplayShapeSpec;
+ private final float mPhysicalPixelDisplaySizeRatio;
+ private final int mDisplayWidth;
+ private final int mDisplayHeight;
+ private final int mRotation;
+ private final int mOffsetX;
+ private final int mOffsetY;
+ private final float mScale;
+
+ private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+ float physicalPixelDisplaySizeRatio, int rotation) {
+ this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio,
+ rotation, 0, 0, 1f);
+ }
+
+ private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight,
+ float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY,
+ float scale) {
+ mDisplayShapeSpec = displayShapeSpec;
+ mDisplayWidth = displayWidth;
+ mDisplayHeight = displayHeight;
+ mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+ mRotation = rotation;
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
+ mScale = scale;
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static DisplayShape fromResources(
+ @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth,
+ int physicalDisplayHeight, int displayWidth, int displayHeight) {
+ final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId);
+ final String spec = getSpecString(res, displayUniqueId);
+ if (spec == null || spec.isEmpty()) {
+ return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound);
+ }
+ final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio(
+ physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight);
+ return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight);
+ }
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static DisplayShape createDefaultDisplayShape(
+ int displayWidth, int displayHeight, boolean isScreenRound) {
+ return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound),
+ 1f, displayWidth, displayHeight);
+ }
+
+ /**
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static DisplayShape fromSpecString(@NonNull String spec,
+ float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) {
+ return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth,
+ displayHeight);
+ }
+
+ private static String createDefaultSpecString(int displayWidth, int displayHeight,
+ boolean isCircular) {
+ final String spec;
+ if (isCircular) {
+ final float xRadius = displayWidth / 2f;
+ final float yRadius = displayHeight / 2f;
+ // Draw a circular display shape.
+ spec = "M0," + yRadius
+ // Draw upper half circle with arcTo command.
+ + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius
+ // Draw lower half circle with arcTo command.
+ + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z";
+ } else {
+ // Draw a rectangular display shape.
+ spec = "M0,0"
+ // Draw top edge.
+ + " L" + displayWidth + ",0"
+ // Draw right edge.
+ + " L" + displayWidth + "," + displayHeight
+ // Draw bottom edge.
+ + " L0," + displayHeight
+ // Draw left edge by close command which draws a line from current position to
+ // the initial points (0,0).
+ + " Z";
+ }
+ return spec;
+ }
+
+ /**
+ * Gets the display shape svg spec string of a display which is determined by the given display
+ * unique id.
+ *
+ * Loads the default config {@link R.string#config_mainDisplayShape} if
+ * {@link R.array#config_displayUniqueIdArray} is not set.
+ *
+ * @hide
+ */
+ public static String getSpecString(Resources res, String displayUniqueId) {
+ final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId);
+ final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray);
+ final String spec;
+ if (index >= 0 && index < array.length()) {
+ spec = array.getString(index);
+ } else {
+ spec = res.getString(R.string.config_mainDisplayShape);
+ }
+ array.recycle();
+ return spec;
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setRotation(int rotation) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale);
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setOffset(int offsetX, int offsetY) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale);
+ }
+
+ /**
+ * @hide
+ */
+ public DisplayShape setScale(float scale) {
+ return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight,
+ mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof DisplayShape) {
+ DisplayShape ds = (DisplayShape) o;
+ return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec)
+ && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight
+ && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio
+ && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX
+ && mOffsetY == ds.mOffsetY && mScale == ds.mScale;
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "DisplayShape{"
+ + " spec=" + mDisplayShapeSpec
+ + " displayWidth=" + mDisplayWidth
+ + " displayHeight=" + mDisplayHeight
+ + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio
+ + " rotation=" + mRotation
+ + " offsetX=" + mOffsetX
+ + " offsetY=" + mOffsetY
+ + " scale=" + mScale + "}";
+ }
+
+ /**
+ * Returns a {@link Path} of the display shape.
+ *
+ * @return a {@link Path} of the display shape.
+ */
+ @NonNull
+ public Path getPath() {
+ return Cache.getPath(this);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mDisplayShapeSpec);
+ dest.writeInt(mDisplayWidth);
+ dest.writeInt(mDisplayHeight);
+ dest.writeFloat(mPhysicalPixelDisplaySizeRatio);
+ dest.writeInt(mRotation);
+ dest.writeInt(mOffsetX);
+ dest.writeInt(mOffsetY);
+ dest.writeFloat(mScale);
+ }
+
+ public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() {
+ @Override
+ public DisplayShape createFromParcel(Parcel in) {
+ final String spec = in.readString8();
+ final int displayWidth = in.readInt();
+ final int displayHeight = in.readInt();
+ final float ratio = in.readFloat();
+ final int rotation = in.readInt();
+ final int offsetX = in.readInt();
+ final int offsetY = in.readInt();
+ final float scale = in.readFloat();
+ return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX,
+ offsetY, scale);
+ }
+
+ @Override
+ public DisplayShape[] newArray(int size) {
+ return new DisplayShape[size];
+ }
+ };
+
+ private static final class Cache {
+ private static final Object CACHE_LOCK = new Object();
+
+ @GuardedBy("CACHE_LOCK")
+ private static String sCachedSpec;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayWidth;
+ @GuardedBy("CACHE_LOCK")
+ private static int sCachedDisplayHeight;
+ @GuardedBy("CACHE_LOCK")
+ private static float sCachedPhysicalPixelDisplaySizeRatio;
+ @GuardedBy("CACHE_LOCK")
+ private static DisplayShape sCachedDisplayShape;
+
+ @GuardedBy("CACHE_LOCK")
+ private static DisplayShape sCacheForPath;
+ @GuardedBy("CACHE_LOCK")
+ private static Path sCachedPath;
+
+ static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio,
+ int displayWidth, int displayHeight) {
+ synchronized (CACHE_LOCK) {
+ if (spec.equals(sCachedSpec)
+ && sCachedDisplayWidth == displayWidth
+ && sCachedDisplayHeight == displayHeight
+ && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) {
+ return sCachedDisplayShape;
+ }
+ }
+
+ final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight,
+ physicalPixelDisplaySizeRatio, ROTATION_0);
+
+ synchronized (CACHE_LOCK) {
+ sCachedSpec = spec;
+ sCachedDisplayWidth = displayWidth;
+ sCachedDisplayHeight = displayHeight;
+ sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio;
+ sCachedDisplayShape = shape;
+ }
+ return shape;
+ }
+
+ static Path getPath(@NonNull DisplayShape shape) {
+ synchronized (CACHE_LOCK) {
+ if (shape.equals(sCacheForPath)) {
+ return sCachedPath;
+ }
+ }
+
+ final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec);
+
+ if (!path.isEmpty()) {
+ final Matrix matrix = new Matrix();
+ if (shape.mRotation != ROTATION_0) {
+ RotationUtils.transformPhysicalToLogicalCoordinates(
+ shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix);
+ }
+ if (shape.mPhysicalPixelDisplaySizeRatio != 1f) {
+ matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio,
+ shape.mPhysicalPixelDisplaySizeRatio);
+ }
+ if (shape.mOffsetX != 0 || shape.mOffsetY != 0) {
+ matrix.postTranslate(shape.mOffsetX, shape.mOffsetY);
+ }
+ if (shape.mScale != 1f) {
+ matrix.postScale(shape.mScale, shape.mScale);
+ }
+ path.transform(matrix);
+ }
+
+ synchronized (CACHE_LOCK) {
+ sCacheForPath = shape;
+ sCachedPath = path;
+ }
+ return path;
+ }
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index e2bc566..0743ccb 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -738,9 +738,8 @@
* If invoked through a package other than a launcher app, returns an empty list.
*
* @param displayId the id of the logical display
- * @param packageName the name of the calling package
*/
- List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName);
+ List<DisplayInfo> getPossibleDisplayInfo(int displayId);
/**
* Called to show global actions.
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index a8cc9b6..c56d618 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -197,6 +197,9 @@
private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
new PrivacyIndicatorBounds();
+ /** The display shape */
+ private DisplayShape mDisplayShape = DisplayShape.NONE;
+
public InsetsState() {
}
@@ -271,6 +274,7 @@
alwaysConsumeSystemBars, calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
+ calculateRelativeDisplayShape(frame),
compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
@@ -335,6 +339,16 @@
return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
}
+ private DisplayShape calculateRelativeDisplayShape(Rect frame) {
+ if (mDisplayFrame.equals(frame)) {
+ return mDisplayShape;
+ }
+ if (frame == null) {
+ return DisplayShape.NONE;
+ }
+ return mDisplayShape.setOffset(-frame.left, -frame.top);
+ }
+
public Insets calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
@@ -589,6 +603,14 @@
return mPrivacyIndicatorBounds;
}
+ public void setDisplayShape(DisplayShape displayShape) {
+ mDisplayShape = displayShape;
+ }
+
+ public DisplayShape getDisplayShape() {
+ return mDisplayShape;
+ }
+
/**
* Modifies the state of this class to exclude a certain type to make it ready for dispatching
* to the client.
@@ -628,6 +650,7 @@
mRoundedCorners = mRoundedCorners.scale(scale);
mRoundedCornerFrame.scale(scale);
mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
+ mDisplayShape = mDisplayShape.setScale(scale);
for (int i = 0; i < SIZE; i++) {
final InsetsSource source = mSources[i];
if (source != null) {
@@ -650,6 +673,7 @@
mRoundedCorners = other.getRoundedCorners();
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+ mDisplayShape = other.getDisplayShape();
if (copySources) {
for (int i = 0; i < SIZE; i++) {
InsetsSource source = other.mSources[i];
@@ -675,6 +699,7 @@
mRoundedCorners = other.getRoundedCorners();
mRoundedCornerFrame.set(other.mRoundedCornerFrame);
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
+ mDisplayShape = other.getDisplayShape();
final ArraySet<Integer> t = toInternalType(types);
for (int i = t.size() - 1; i >= 0; i--) {
final int type = t.valueAt(i);
@@ -807,6 +832,7 @@
pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
pw.println(newPrefix + "mRoundedCornerFrame=" + mRoundedCornerFrame);
pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
+ pw.println(newPrefix + "mDisplayShape=" + mDisplayShape);
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source == null) continue;
@@ -911,7 +937,8 @@
|| !mDisplayCutout.equals(state.mDisplayCutout)
|| !mRoundedCorners.equals(state.mRoundedCorners)
|| !mRoundedCornerFrame.equals(state.mRoundedCornerFrame)
- || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
+ || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)
+ || !mDisplayShape.equals(state.mDisplayShape)) {
return false;
}
for (int i = 0; i < SIZE; i++) {
@@ -941,7 +968,7 @@
@Override
public int hashCode() {
return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
- mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame);
+ mRoundedCorners, mPrivacyIndicatorBounds, mRoundedCornerFrame, mDisplayShape);
}
public InsetsState(Parcel in) {
@@ -961,6 +988,7 @@
dest.writeTypedObject(mRoundedCorners, flags);
mRoundedCornerFrame.writeToParcel(dest, flags);
dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
+ dest.writeTypedObject(mDisplayShape, flags);
}
public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
@@ -981,6 +1009,7 @@
mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
mRoundedCornerFrame.readFromParcel(in);
mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
+ mDisplayShape = in.readTypedObject(DisplayShape.CREATOR);
}
@Override
@@ -998,6 +1027,7 @@
+ ", mRoundedCorners=" + mRoundedCorners
+ " mRoundedCornerFrame=" + mRoundedCornerFrame
+ ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+ + ", mDisplayShape=" + mDisplayShape
+ ", mSources= { " + joiner
+ " }";
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 57b2d39..33ea92d 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -947,108 +947,112 @@
+ " left=" + (mWindowSpaceLeft != mLocation[0])
+ " top=" + (mWindowSpaceTop != mLocation[1]));
- mVisible = mRequestedVisible;
- mWindowSpaceLeft = mLocation[0];
- mWindowSpaceTop = mLocation[1];
- mSurfaceWidth = myWidth;
- mSurfaceHeight = myHeight;
- mFormat = mRequestedFormat;
- mAlpha = alpha;
- mLastWindowVisibility = mWindowVisibility;
- mTransformHint = viewRoot.getBufferTransformHint();
- mSubLayer = mRequestedSubLayer;
-
- mScreenRect.left = mWindowSpaceLeft;
- mScreenRect.top = mWindowSpaceTop;
- mScreenRect.right = mWindowSpaceLeft + getWidth();
- mScreenRect.bottom = mWindowSpaceTop + getHeight();
- if (translator != null) {
- translator.translateRectInAppWindowToScreen(mScreenRect);
- }
-
- final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
- mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
- // Collect all geometry changes and apply these changes on the RenderThread worker
- // via the RenderNode.PositionUpdateListener.
- final Transaction surfaceUpdateTransaction = new Transaction();
- if (creating) {
- updateOpaqueFlag();
- final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
- createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
- } else if (mSurfaceControl == null) {
- return;
- }
-
- final boolean redrawNeeded = sizeChanged || creating || hintChanged
- || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
- boolean shouldSyncBuffer =
- redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
- SyncBufferTransactionCallback syncBufferTransactionCallback = null;
- if (shouldSyncBuffer) {
- syncBufferTransactionCallback = new SyncBufferTransactionCallback();
- mBlastBufferQueue.syncNextTransaction(
- false /* acquireSingleBuffer */,
- syncBufferTransactionCallback::onTransactionReady);
- }
-
- final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
- creating, sizeChanged, hintChanged, relativeZChanged,
- surfaceUpdateTransaction);
-
try {
- SurfaceHolder.Callback[] callbacks = null;
+ mVisible = mRequestedVisible;
+ mWindowSpaceLeft = mLocation[0];
+ mWindowSpaceTop = mLocation[1];
+ mSurfaceWidth = myWidth;
+ mSurfaceHeight = myHeight;
+ mFormat = mRequestedFormat;
+ mAlpha = alpha;
+ mLastWindowVisibility = mWindowVisibility;
+ mTransformHint = viewRoot.getBufferTransformHint();
+ mSubLayer = mRequestedSubLayer;
- final boolean surfaceChanged = creating;
- if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
- mSurfaceCreated = false;
- notifySurfaceDestroyed();
+ mScreenRect.left = mWindowSpaceLeft;
+ mScreenRect.top = mWindowSpaceTop;
+ mScreenRect.right = mWindowSpaceLeft + getWidth();
+ mScreenRect.bottom = mWindowSpaceTop + getHeight();
+ if (translator != null) {
+ translator.translateRectInAppWindowToScreen(mScreenRect);
}
- copySurface(creating /* surfaceControlCreated */, sizeChanged);
+ final Rect surfaceInsets = viewRoot.mWindowAttributes.surfaceInsets;
+ mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);
+ // Collect all geometry changes and apply these changes on the RenderThread worker
+ // via the RenderNode.PositionUpdateListener.
+ final Transaction surfaceUpdateTransaction = new Transaction();
+ if (creating) {
+ updateOpaqueFlag();
+ final String name = "SurfaceView[" + viewRoot.getTitle().toString() + "]";
+ createBlastSurfaceControls(viewRoot, name, surfaceUpdateTransaction);
+ } else if (mSurfaceControl == null) {
+ return;
+ }
- if (mVisible && mSurface.isValid()) {
- if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
- mSurfaceCreated = true;
- mIsCreating = true;
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "visibleChanged -- surfaceCreated");
- callbacks = getSurfaceCallbacks();
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceCreated(mSurfaceHolder);
- }
+ final boolean redrawNeeded = sizeChanged || creating || hintChanged
+ || (mVisible && !mDrawFinished) || alphaChanged || relativeZChanged;
+ boolean shouldSyncBuffer =
+ redrawNeeded && viewRoot.wasRelayoutRequested() && viewRoot.isInLocalSync();
+ SyncBufferTransactionCallback syncBufferTransactionCallback = null;
+ if (shouldSyncBuffer) {
+ syncBufferTransactionCallback = new SyncBufferTransactionCallback();
+ mBlastBufferQueue.syncNextTransaction(
+ false /* acquireSingleBuffer */,
+ syncBufferTransactionCallback::onTransactionReady);
+ }
+
+ final boolean realSizeChanged = performSurfaceTransaction(viewRoot, translator,
+ creating, sizeChanged, hintChanged, relativeZChanged,
+ surfaceUpdateTransaction);
+
+ try {
+ SurfaceHolder.Callback[] callbacks = null;
+
+ final boolean surfaceChanged = creating;
+ if (mSurfaceCreated && (surfaceChanged || (!mVisible && visibleChanged))) {
+ mSurfaceCreated = false;
+ notifySurfaceDestroyed();
}
- if (creating || formatChanged || sizeChanged || hintChanged
- || visibleChanged || realSizeChanged) {
- if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
- + "surfaceChanged -- format=" + mFormat
- + " w=" + myWidth + " h=" + myHeight);
- if (callbacks == null) {
+
+ copySurface(creating /* surfaceControlCreated */, sizeChanged);
+
+ if (mVisible && mSurface.isValid()) {
+ if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
+ mSurfaceCreated = true;
+ mIsCreating = true;
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "visibleChanged -- surfaceCreated");
callbacks = getSurfaceCallbacks();
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceCreated(mSurfaceHolder);
+ }
}
- for (SurfaceHolder.Callback c : callbacks) {
- c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ if (creating || formatChanged || sizeChanged || hintChanged
+ || visibleChanged || realSizeChanged) {
+ if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ + "surfaceChanged -- format=" + mFormat
+ + " w=" + myWidth + " h=" + myHeight);
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
+ for (SurfaceHolder.Callback c : callbacks) {
+ c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
+ }
}
- }
- if (redrawNeeded) {
- if (DEBUG) {
- Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
- }
- if (callbacks == null) {
- callbacks = getSurfaceCallbacks();
- }
+ if (redrawNeeded) {
+ if (DEBUG) {
+ Log.i(TAG, System.identityHashCode(this) + " surfaceRedrawNeeded");
+ }
+ if (callbacks == null) {
+ callbacks = getSurfaceCallbacks();
+ }
- if (shouldSyncBuffer) {
- handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
- } else {
- handleSyncNoBuffer(callbacks);
+ if (shouldSyncBuffer) {
+ handleSyncBufferCallback(callbacks, syncBufferTransactionCallback);
+ } else {
+ handleSyncNoBuffer(callbacks);
+ }
}
}
+ } finally {
+ mIsCreating = false;
+ if (mSurfaceControl != null && !mSurfaceCreated) {
+ releaseSurfaces(false /* releaseSurfacePackage*/);
+ }
}
- } finally {
- mIsCreating = false;
- if (mSurfaceControl != null && !mSurfaceCreated) {
- releaseSurfaces(false /* releaseSurfacePackage*/);
- }
+ } catch (Exception ex) {
+ Log.e(TAG, "Exception configuring surface", ex);
}
if (DEBUG) Log.v(
TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 03b25c2..8de15c1 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -42,7 +42,6 @@
import android.content.Intent;
import android.graphics.Insets;
import android.graphics.Rect;
-import android.util.SparseArray;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.EditorInfo;
@@ -83,6 +82,7 @@
@Nullable private final DisplayCutout mDisplayCutout;
@Nullable private final RoundedCorners mRoundedCorners;
@Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds;
+ @Nullable private final DisplayShape mDisplayShape;
/**
* In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -115,24 +115,9 @@
public static final @NonNull WindowInsets CONSUMED;
static {
- CONSUMED = new WindowInsets((Rect) null, null, false, false, null);
- }
-
- /**
- * Construct a new WindowInsets from individual insets.
- *
- * A {@code null} inset indicates that the respective inset is consumed.
- *
- * @hide
- * @deprecated Use {@link WindowInsets(SparseArray, SparseArray, boolean, boolean, DisplayCutout)}
- */
- @Deprecated
- public WindowInsets(Rect systemWindowInsetsRect, Rect stableInsetsRect, boolean isRound,
- boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
- this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
- createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
- isRound, alwaysConsumeSystemBars, displayCutout, null, null,
- systemBars(), false /* compatIgnoreVisibility */);
+ CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null),
+ createCompatVisibilityMap(createCompatTypeMap(null)), false, false, null, null,
+ null, null, systemBars(), false);
}
/**
@@ -154,6 +139,7 @@
boolean alwaysConsumeSystemBars, DisplayCutout displayCutout,
RoundedCorners roundedCorners,
PrivacyIndicatorBounds privacyIndicatorBounds,
+ DisplayShape displayShape,
@InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
@@ -177,6 +163,7 @@
mRoundedCorners = roundedCorners;
mPrivacyIndicatorBounds = privacyIndicatorBounds;
+ mDisplayShape = displayShape;
}
/**
@@ -191,6 +178,7 @@
src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
src.mRoundedCorners,
src.mPrivacyIndicatorBounds,
+ src.mDisplayShape,
src.mCompatInsetsTypes,
src.mCompatIgnoreVisibility);
}
@@ -244,15 +232,18 @@
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
- null, null, systemBars(), false /* compatIgnoreVisibility */);
+ null, null, null, systemBars(), false /* compatIgnoreVisibility */);
}
/**
* Creates a indexOf(type) -> inset map for which the {@code insets} is just mapped to
* {@link Type#statusBars()} and {@link Type#navigationBars()}, depending on the
* location of the inset.
+ *
+ * @hide
*/
- private static Insets[] createCompatTypeMap(@Nullable Rect insets) {
+ @VisibleForTesting
+ public static Insets[] createCompatTypeMap(@Nullable Rect insets) {
if (insets == null) {
return null;
}
@@ -271,6 +262,10 @@
Insets.of(insets.left, 0, insets.right, insets.bottom);
}
+ /**
+ * @hide
+ */
+ @VisibleForTesting
private static boolean[] createCompatVisibilityMap(@Nullable Insets[] typeInsetsMap) {
boolean[] typeVisibilityMap = new boolean[SIZE];
if (typeInsetsMap == null) {
@@ -533,6 +528,17 @@
}
/**
+ * Returns the display shape in the coordinate space of the window.
+ *
+ * @return the display shape
+ * @see DisplayShape
+ */
+ @Nullable
+ public DisplayShape getDisplayShape() {
+ return mDisplayShape;
+ }
+
+ /**
* Returns a copy of this WindowInsets with the cutout fully consumed.
*
* @return A modified copy of this WindowInsets
@@ -547,7 +553,7 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds,
+ null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -602,7 +608,7 @@
// it.
(mCompatInsetsTypes & displayCutout()) != 0
? null : displayCutoutCopyConstructorArgument(this),
- mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes,
+ mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes,
mCompatIgnoreVisibility);
}
@@ -911,6 +917,8 @@
result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds="
+ mPrivacyIndicatorBounds : "");
result.append("\n ");
+ result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : "");
+ result.append("\n ");
result.append("compatInsetsTypes=" + mCompatInsetsTypes);
result.append("\n ");
result.append("compatIgnoreVisibility=" + mCompatIgnoreVisibility);
@@ -1018,6 +1026,7 @@
mPrivacyIndicatorBounds == null
? null
: mPrivacyIndicatorBounds.inset(left, top, right, bottom),
+ mDisplayShape,
mCompatInsetsTypes, mCompatIgnoreVisibility);
}
@@ -1037,7 +1046,8 @@
&& Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap)
&& Objects.equals(mDisplayCutout, that.mDisplayCutout)
&& Objects.equals(mRoundedCorners, that.mRoundedCorners)
- && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds);
+ && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds)
+ && Objects.equals(mDisplayShape, that.mDisplayShape);
}
@Override
@@ -1045,7 +1055,7 @@
return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap),
Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners,
mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed,
- mDisplayCutoutConsumed, mPrivacyIndicatorBounds);
+ mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape);
}
@@ -1106,6 +1116,7 @@
private DisplayCutout mDisplayCutout;
private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
+ private DisplayShape mDisplayShape = DisplayShape.NONE;
private boolean mIsRound;
private boolean mAlwaysConsumeSystemBars;
@@ -1137,6 +1148,7 @@
mIsRound = insets.mIsRound;
mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars;
mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds;
+ mDisplayShape = insets.mDisplayShape;
}
/**
@@ -1381,6 +1393,19 @@
return this;
}
+ /**
+ * Sets the display shape.
+ *
+ * @see #getDisplayShape().
+ * @param displayShape the display shape.
+ * @return itself.
+ */
+ @NonNull
+ public Builder setDisplayShape(@NonNull DisplayShape displayShape) {
+ mDisplayShape = displayShape;
+ return this;
+ }
+
/** @hide */
@NonNull
public Builder setRound(boolean round) {
@@ -1405,7 +1430,8 @@
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners,
- mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */);
+ mPrivacyIndicatorBounds, mDisplayShape, systemBars(),
+ false /* compatIgnoreVisibility */);
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 6dc9011..5c4305c 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -366,7 +366,7 @@
List<DisplayInfo> possibleDisplayInfos;
try {
possibleDisplayInfos = WindowManagerGlobal.getWindowManagerService()
- .getPossibleDisplayInfo(displayId, mContext.getPackageName());
+ .getPossibleDisplayInfo(displayId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index c2da638..a35e13e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,6 +421,53 @@
return false;
}
+ /**
+ * Releases temporary-for-animation surfaces referenced by this to potentially free up memory.
+ * This includes root-leash and snapshots.
+ */
+ public void releaseAnimSurfaces() {
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ final Change c = mChanges.get(i);
+ if (c.mSnapshot != null) {
+ c.mSnapshot.release();
+ c.mSnapshot = null;
+ }
+ }
+ if (mRootLeash != null) {
+ mRootLeash.release();
+ }
+ }
+
+ /**
+ * Releases ALL the surfaces referenced by this to potentially free up memory. Do NOT use this
+ * if the surface-controls get stored and used elsewhere in the process. To just release
+ * temporary-for-animation surfaces, use {@link #releaseAnimSurfaces}.
+ */
+ public void releaseAllSurfaces() {
+ releaseAnimSurfaces();
+ for (int i = mChanges.size() - 1; i >= 0; --i) {
+ mChanges.get(i).getLeash().release();
+ }
+ }
+
+ /**
+ * Makes a copy of this as if it were parcel'd and unparcel'd. This implies that surfacecontrol
+ * refcounts are incremented which allows the "remote" receiver to release them without breaking
+ * the caller's references. Use this only if you need to "send" this to a local function which
+ * assumes it is being called from a remote caller.
+ */
+ public TransitionInfo localRemoteCopy() {
+ final TransitionInfo out = new TransitionInfo(mType, mFlags);
+ for (int i = 0; i < mChanges.size(); ++i) {
+ out.mChanges.add(mChanges.get(i).localRemoteCopy());
+ }
+ out.mRootLeash = mRootLeash != null ? new SurfaceControl(mRootLeash, "localRemote") : null;
+ // Doesn't have any native stuff, so no need for actual copy
+ out.mOptions = mOptions;
+ out.mRootOffset.set(mRootOffset);
+ return out;
+ }
+
/** Represents the change a WindowContainer undergoes during a transition */
public static final class Change implements Parcelable {
private final WindowContainerToken mContainer;
@@ -473,6 +520,27 @@
mSnapshotLuma = in.readFloat();
}
+ private Change localRemoteCopy() {
+ final Change out = new Change(mContainer, new SurfaceControl(mLeash, "localRemote"));
+ out.mParent = mParent;
+ out.mLastParent = mLastParent;
+ out.mMode = mMode;
+ out.mFlags = mFlags;
+ out.mStartAbsBounds.set(mStartAbsBounds);
+ out.mEndAbsBounds.set(mEndAbsBounds);
+ out.mEndRelOffset.set(mEndRelOffset);
+ out.mTaskInfo = mTaskInfo;
+ out.mAllowEnterPip = mAllowEnterPip;
+ out.mStartRotation = mStartRotation;
+ out.mEndRotation = mEndRotation;
+ out.mEndFixedRotation = mEndFixedRotation;
+ out.mRotationAnimation = mRotationAnimation;
+ out.mBackgroundColor = mBackgroundColor;
+ out.mSnapshot = mSnapshot != null ? new SurfaceControl(mSnapshot, "localRemote") : null;
+ out.mSnapshotLuma = mSnapshotLuma;
+ return out;
+ }
+
/** Sets the parent of this change's container. The parent must be a participant or null. */
public void setParent(@Nullable WindowContainerToken parent) {
mParent = parent;
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index 6ceccd1..260d1a2 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -192,7 +192,7 @@
* @param name the non-check overlay name
* @return the valid overlay name
*/
- private static String checkOverlayNameValid(@NonNull String name) {
+ public static String checkOverlayNameValid(@NonNull String name) {
final String overlayName =
Preconditions.checkStringNotEmpty(
name, "overlayName should be neither empty nor null string");
diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
index 1e3714e..8cb568d 100644
--- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl
+++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl
@@ -40,7 +40,6 @@
parcelable InitParams {
IBinder token;
IInputMethodPrivilegedOperations privilegedOperations;
- int configChanges;
int navigationBarFlags;
}
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 4b1753a..9cb2e68 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -17,7 +17,7 @@
package com.android.internal.telephony;
import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
+import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.DataConnectionRealTimeInfo;
@@ -62,7 +62,7 @@
void onPhoneCapabilityChanged(in PhoneCapability capability);
void onActiveDataSubIdChanged(in int subId);
void onRadioPowerStateChanged(in int state);
- void onCallAttributesChanged(in CallAttributes callAttributes);
+ void onCallStatesChanged(in List<CallState> callStateList);
@SuppressWarnings(value={"untyped-collection"})
void onEmergencyNumberListChanged(in Map emergencyNumberList);
void onOutgoingEmergencyCall(in EmergencyNumber placedEmergencyNumber, int subscriptionId);
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index c7fa757..7ba2686 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -66,8 +66,8 @@
void notifyCellLocationForSubscriber(in int subId, in CellIdentity cellLocation);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
- void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
- int foregroundCallState, int backgroundCallState);
+ void notifyPreciseCallState(int phoneId, int subId, in int[] callStates, in String[] imsCallIds,
+ in int[] imsCallServiceTypes, in int[] imsCallTypes);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index f140e79..1bc903a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -40,6 +40,8 @@
cppflags: ["-Wno-conversion-null"],
+ cpp_std: "gnu++20",
+
srcs: [
"android_animation_PropertyValuesHolder.cpp",
"android_os_SystemClock.cpp",
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index a8d7231..29560dc 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -96,7 +96,7 @@
}
bool ForEachFile(const std::string& /* root_path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+ const std::function<void(StringPiece, FileType)>& /* f */) const {
return true;
}
@@ -402,7 +402,7 @@
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
-static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+static jboolean NativeIsUpToDate(jlong ptr) {
auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
auto apk_assets = scoped_apk_assets->get();
return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
@@ -500,24 +500,28 @@
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
- {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
- (void*)NativeLoad},
- {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
- {"nativeLoadFd",
- "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
- (void*)NativeLoadFromFd},
- {"nativeLoadFdOffsets",
- "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
- (void*)NativeLoadFromFdOffset},
- {"nativeDestroy", "(J)V", (void*)NativeDestroy},
- {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
- {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
- {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
- {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
- {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
- {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
- (void*)NativeGetOverlayableInfo},
- {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
+ {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+ (void*)NativeLoad},
+ {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J",
+ (void*)NativeLoadEmpty},
+ {"nativeLoadFd",
+ "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/"
+ "AssetsProvider;)J",
+ (void*)NativeLoadFromFd},
+ {"nativeLoadFdOffsets",
+ "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/"
+ "AssetsProvider;)J",
+ (void*)NativeLoadFromFdOffset},
+ {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+ {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+ {"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
+ {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+ // @CriticalNative
+ {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+ {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+ {"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
+ (void*)NativeGetOverlayableInfo},
+ {"nativeDefinesOverlayable", "(J)Z", (void*)NativeDefinesOverlayable},
};
int register_android_content_res_ApkAssets(JNIEnv* env) {
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index cb97698..939a0e4 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -243,6 +243,23 @@
}
}
+static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
+ jobject sensorList) {
+ SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+ const ListOffsets &listOffsets(gListOffsets);
+
+ Vector<Sensor> nativeList;
+
+ mgr->getRuntimeSensorList(deviceId, nativeList);
+
+ ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
+ for (size_t i = 0; i < nativeList.size(); ++i) {
+ jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
+ // add to list
+ env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
+ }
+}
+
static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@
//----------------------------------------------------------------------------
static const JNINativeMethod gSystemSensorManagerMethods[] = {
- {"nativeClassInit",
- "()V",
- (void*)nativeClassInit },
- {"nativeCreate",
- "(Ljava/lang/String;)J",
- (void*)nativeCreate },
+ {"nativeClassInit", "()V", (void *)nativeClassInit},
+ {"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
- {"nativeGetSensorAtIndex",
- "(JLandroid/hardware/Sensor;I)Z",
- (void*)nativeGetSensorAtIndex },
+ {"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
+ (void *)nativeGetSensorAtIndex},
- {"nativeGetDynamicSensors",
- "(JLjava/util/List;)V",
- (void*)nativeGetDynamicSensors },
+ {"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
- {"nativeIsDataInjectionEnabled",
- "(J)Z",
- (void*)nativeIsDataInjectionEnabled },
+ {"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
- {"nativeCreateDirectChannel",
- "(JJIILandroid/hardware/HardwareBuffer;)I",
- (void*)nativeCreateDirectChannel },
+ {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
- {"nativeDestroyDirectChannel",
- "(JI)V",
- (void*)nativeDestroyDirectChannel },
+ {"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
+ (void *)nativeCreateDirectChannel},
- {"nativeConfigDirectChannel",
- "(JIII)I",
- (void*)nativeConfigDirectChannel },
+ {"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
- {"nativeSetOperationParameter",
- "(JII[F[I)I",
- (void*)nativeSetOperationParameter },
+ {"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
+
+ {"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
};
static const JNINativeMethod gBaseEventQueueMethods[] = {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 4650000..7393c6f 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -977,12 +977,11 @@
optional int32 profile = 2;
}
repeated UserProfile user_profile_group_ids = 4;
- repeated int32 visible_users_array = 5;
// current_user contains the id of the current user, while current_profiles contains the ids of
// both the current user and its profiles (if any)
- optional int32 current_user = 6;
- repeated int32 current_profiles = 7;
+ optional int32 current_user = 5;
+ repeated int32 current_profiles = 6;
}
// sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ecc3979..ad8f7fb 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6287,12 +6287,12 @@
<!-- Allows a regular application to use {@link android.app.Service#startForeground
Service.startForeground} with the type "specialUse".
- <p>Protection level: signature|appop|instant
+ <p>Protection level: normal|appop|instant
-->
<permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"
android:description="@string/permdesc_foregroundServiceSpecialUse"
android:label="@string/permlab_foregroundServiceSpecialUse"
- android:protectionLevel="signature|appop|instant" />
+ android:protectionLevel="normal|appop|instant" />
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
@@ -7291,6 +7291,10 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.pm.GentleUpdateHelper$Service"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service
android:name="com.android.server.autofill.AutofillCompatAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d832bc..7946493 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,6 +8955,9 @@
<!-- Flag indicating whether a recognition service can be selected as default. The default
value of this flag is true. -->
<attr name="selectableAsDefault" format="boolean" />
+ <!-- The maximal number of recognition sessions ongoing at the same time.
+ The default value is 1, meaning no concurrency. -->
+ <attr name="maxConcurrentSessionsCount" format="integer" />
</declare-styleable>
<!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6460007..eb70344 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1705,8 +1705,7 @@
-->
<flag name="systemExempted" value="0x400" />
<!-- "Short service" foreground service type. See
- TODO: Change it to a real link
- {@code android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
+ {@link android.content.pm.ServiceInfo#FOREGROUND_SERVICE_TYPE_SHORT_SERVICE}.
for more details.
-->
<flag name="shortService" value="0x800" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ccce9ba..2ab5b75 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1828,6 +1828,14 @@
config_enableFusedLocationOverlay is false. -->
<string name="config_fusedLocationProviderPackageName" translatable="false">com.android.location.fused</string>
+ <!-- If true will use the GNSS hardware implementation to service the GPS_PROVIDER. If false
+ will allow the GPS_PROVIDER to be replaced by an app at run-time (restricted to the package
+ specified by config_gnssLocationProviderPackageName). -->
+ <bool name="config_useGnssHardwareProvider" translatable="false">true</bool>
+ <!-- Package name providing GNSS location support. Used only when
+ config_useGnssHardwareProvider is false. -->
+ <string name="config_gnssLocationProviderPackageName" translatable="false">@null</string>
+
<!-- Default value for the ADAS GNSS Location Enabled setting if this setting has never been
set before. -->
<bool name="config_defaultAdasGnssLocationEnabled" translatable="false">false</bool>
@@ -5977,4 +5985,35 @@
<string-array translatable="false" name="config_fontManagerServiceCerts">
</string-array>
+ <!-- A string config in svg path format for the main display shape.
+ (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+ This config must be set unless:
+ 1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+ and the system will auto-generate a circular shape.
+ 2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+ (@see DisplayShape#createDefaultDisplayShape)
+
+ Note: If the display supports multiple resolutions, please define the path config based on
+ the highest resolution so that it can be scaled correctly in each resolution. -->
+ <string name="config_mainDisplayShape" translatable="false"></string>
+
+ <!-- A string config in svg path format for the secondary display shape.
+ (@see https://www.w3.org/TR/SVG/paths.html#PathData).
+
+ This config must be set unless:
+ 1. {@link Configuration#isScreenRound} is true which means the display shape is circular
+ and the system will auto-generate a circular shape.
+ 2. The display has no rounded corner and the system will auto-generate a rectangular shape.
+ (@see DisplayShape#createDefaultDisplayShape)
+
+ Note: If the display supports multiple resolutions, please define the path config based on
+ the highest resolution so that it can be scaled correctly in each resolution. -->
+ <string name="config_secondaryDisplayShape" translatable="false"></string>
+
+ <!-- The display shape config for each display in a multi-display device. -->
+ <string-array name="config_displayShapeArray" translatable="false">
+ <item>@string/config_mainDisplayShape</item>
+ <item>@string/config_secondaryDisplayShape</item>
+ </string-array>
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 61229cb..bc5878a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -117,6 +117,7 @@
<public name="accessibilityDataPrivate" />
<public name="enableTextStylingShortcuts" />
<public name="targetDisplayCategory"/>
+ <public name="maxConcurrentSessionsCount" />
</staging-public-group>
<staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ae033ca..168806a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1963,6 +1963,7 @@
<java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
<java-symbol type="bool" name="config_defaultAdasGnssLocationEnabled" />
<java-symbol type="bool" name="config_enableFusedLocationOverlay" />
+ <java-symbol type="bool" name="config_useGnssHardwareProvider" />
<java-symbol type="bool" name="config_enableGeocoderOverlay" />
<java-symbol type="bool" name="config_enableGeofenceOverlay" />
<java-symbol type="bool" name="config_enableNetworkLocationOverlay" />
@@ -2125,6 +2126,7 @@
<java-symbol type="string" name="config_datause_iface" />
<java-symbol type="string" name="config_activityRecognitionHardwarePackageName" />
<java-symbol type="string" name="config_fusedLocationProviderPackageName" />
+ <java-symbol type="string" name="config_gnssLocationProviderPackageName" />
<java-symbol type="string" name="config_geocoderProviderPackageName" />
<java-symbol type="string" name="config_geofenceProviderPackageName" />
<java-symbol type="string" name="config_networkLocationProviderPackageName" />
@@ -4913,4 +4915,8 @@
<java-symbol type="dimen" name="status_bar_height_default" />
<java-symbol type="string" name="default_card_name"/>
+
+ <java-symbol type="string" name="config_mainDisplayShape"/>
+ <java-symbol type="string" name="config_secondaryDisplayShape"/>
+ <java-symbol type="array" name="config_displayShapeArray" />
</resources>
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
new file mode 100644
index 0000000..11afd04
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorConfigTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.os.Parcel;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.os.BackgroundThread;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Duration;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorConfigTest {
+
+ private static final String SENSOR_NAME = "VirtualSensorName";
+ private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private VirtualSensor.SensorStateChangeCallback mSensorCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void parcelAndUnparcel_matches() {
+ final VirtualSensorConfig originalConfig =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setVendor(SENSOR_VENDOR)
+ .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+ .build();
+ final Parcel parcel = Parcel.obtain();
+ originalConfig.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final VirtualSensorConfig recreatedConfig =
+ VirtualSensorConfig.CREATOR.createFromParcel(parcel);
+ assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
+ assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
+ assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
+ assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
+ }
+
+ @Test
+ public void sensorConfig_onlyRequiredFields() {
+ final VirtualSensorConfig config =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
+ assertThat(config.getVendor()).isNull();
+ assertThat(config.getStateChangeCallback()).isNull();
+ }
+
+ @Test
+ public void sensorConfig_sensorCallbackInvocation() throws Exception {
+ final VirtualSensorConfig config =
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
+ .build();
+
+ final Duration samplingPeriod = Duration.ofMillis(123);
+ final Duration batchLatency = Duration.ofMillis(456);
+
+ config.getStateChangeCallback().onStateChanged(true,
+ (int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
+ (int) MILLISECONDS.toMicros(batchLatency.toMillis()));
+
+ verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
+ }
+}
diff --git a/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
new file mode 100644
index 0000000..a9583fd
--- /dev/null
+++ b/core/tests/coretests/src/android/companion/virtual/sensor/VirtualSensorEventTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.virtual.sensor;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class VirtualSensorEventTest {
+
+ private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
+ private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
+
+ @Test
+ public void parcelAndUnparcel_matches() {
+ final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+ .setTimestampNanos(TIMESTAMP_NANOS)
+ .build();
+ final Parcel parcel = Parcel.obtain();
+ originalEvent.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+ final VirtualSensorEvent recreatedEvent =
+ VirtualSensorEvent.CREATOR.createFromParcel(parcel);
+ assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
+ assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
+ }
+
+ @Test
+ public void sensorEvent_nullValues() {
+ assertThrows(
+ IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
+ }
+
+ @Test
+ public void sensorEvent_noValues() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new VirtualSensorEvent.Builder(new float[0]).build());
+ }
+
+ @Test
+ public void sensorEvent_noTimestamp_usesCurrentTime() {
+ final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
+ assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+ assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
+ assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
+ }
+
+ @Test
+ public void sensorEvent_created() {
+ final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
+ .setTimestampNanos(TIMESTAMP_NANOS)
+ .build();
+ assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
+ assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
+ }
+}
diff --git a/core/tests/coretests/src/android/graphics/TypefaceTest.java b/core/tests/coretests/src/android/graphics/TypefaceTest.java
index a528c19..6bf8f56 100644
--- a/core/tests/coretests/src/android/graphics/TypefaceTest.java
+++ b/core/tests/coretests/src/android/graphics/TypefaceTest.java
@@ -203,16 +203,22 @@
fallbackMap);
SharedMemory sharedMemory = Typeface.serializeFontMap(systemFontMap);
Map<String, Typeface> copiedFontMap = new ArrayMap<>();
- Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
- copiedFontMap);
- assertEquals(systemFontMap.size(), copiedFontMap.size());
- for (String key : systemFontMap.keySet()) {
- assertTrue(copiedFontMap.containsKey(key));
- Typeface original = systemFontMap.get(key);
- Typeface copied = copiedFontMap.get(key);
- assertEquals(original.getStyle(), copied.getStyle());
- assertEquals(original.getWeight(), copied.getWeight());
- assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+ try {
+ Typeface.deserializeFontMap(sharedMemory.mapReadOnly().order(ByteOrder.BIG_ENDIAN),
+ copiedFontMap);
+ assertEquals(systemFontMap.size(), copiedFontMap.size());
+ for (String key : systemFontMap.keySet()) {
+ assertTrue(copiedFontMap.containsKey(key));
+ Typeface original = systemFontMap.get(key);
+ Typeface copied = copiedFontMap.get(key);
+ assertEquals(original.getStyle(), copied.getStyle());
+ assertEquals(original.getWeight(), copied.getWeight());
+ assertEquals(measureText(original, "hello"), measureText(copied, "hello"), 1e-6);
+ }
+ } finally {
+ for (Typeface typeface : copiedFontMap.values()) {
+ typeface.releaseNativeObjectForTest();
+ }
}
}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index f7ca822..0c7ff4a 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -16,6 +16,7 @@
package android.os;
+import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;
import static android.os.VibrationEffect.VibrationParameter.targetAmplitude;
import static android.os.VibrationEffect.VibrationParameter.targetFrequency;
@@ -48,6 +49,7 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.time.Duration;
+import java.util.Arrays;
@Presubmit
@RunWith(MockitoJUnitRunner.class)
@@ -62,16 +64,363 @@
private static final int TEST_AMPLITUDE = 100;
private static final long[] TEST_TIMINGS = new long[] { 100, 100, 200 };
private static final int[] TEST_AMPLITUDES =
- new int[] { 255, 0, VibrationEffect.DEFAULT_AMPLITUDE };
+ new int[] { 255, 0, DEFAULT_AMPLITUDE };
private static final VibrationEffect TEST_ONE_SHOT =
VibrationEffect.createOneShot(TEST_TIMING, TEST_AMPLITUDE);
private static final VibrationEffect DEFAULT_ONE_SHOT =
- VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
+ VibrationEffect.createOneShot(TEST_TIMING, DEFAULT_AMPLITUDE);
private static final VibrationEffect TEST_WAVEFORM =
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
@Test
+ public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnEvenIndices() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4, 5},
+ /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {1, 2, 3, 4, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesOnOddIndices() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4, 5},
+ /* amplitudes= */ new int[] {
+ DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 1, 2, 3, 4, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheStart() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {0, 0, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {3, 3};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_zeroAmplitudesAtTheEnd() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, 0, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 1, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_allDefaultAmplitudes() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {
+ DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 6};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_allZeroAmplitudes() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* amplitudes= */ new int[] {0, 0, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {6};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_sparsedZeroAmplitudes() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4, 5, 6, 7},
+ /* amplitudes= */ new int[] {
+ 0, 0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {3, 3, 4, 11, 7};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithDefaultAmplitude() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1},
+ /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 1};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_oneTimingWithZeroAmplitude() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1},
+ /* amplitudes= */ new int[] {0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {1};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_repeating() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4, 5},
+ /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+ /* repeatIndex= */ 0);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4, 5},
+ /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE, 0, DEFAULT_AMPLITUDE, 0},
+ /* repeatIndex= */ 3);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2},
+ /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ 1);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsAndAmplitudes_badAmplitude() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1},
+ /* amplitudes= */ new int[] {200},
+ /* repeatIndex= */ -1);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_nonZeroTimings() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {1, 2, 3};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_oneValue() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {5},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_zeroesAtTheEnd() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 0, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {1, 2, 3, 0, 0};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_zeroesAtTheStart() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {0, 0, 1, 2, 3},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 0, 1, 2, 3};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_zeroesAtTheMiddle() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 0, 0, 3, 4, 5},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {1, 2, 0, 0, 3, 4, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_sparsedZeroes() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+ /* repeatIndex= */ -1);
+ long[] expectedPattern = new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_timingsOnly_repeating() {
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {0, 1, 2, 0, 0, 3, 4, 5, 0},
+ /* repeatIndex= */ 0);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.createWaveform(
+ /* timings= */ new long[] {1, 2, 3, 4},
+ /* repeatIndex= */ 2);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_notPatternPased() {
+ VibrationEffect effect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_oneShot_defaultAmplitude() {
+ VibrationEffect effect = VibrationEffect.createOneShot(
+ /* milliseconds= */ 5, /* ampliutde= */ DEFAULT_AMPLITUDE);
+ long[] expectedPattern = new long[] {0, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_oneShot_badAmplitude() {
+ VibrationEffect effect = VibrationEffect.createOneShot(
+ /* milliseconds= */ 5, /* ampliutde= */ 50);
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_composition_noOffDuration() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {5},
+ /* repeatIndex= */ -1))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {2, 3},
+ /* repeatIndex= */ -1))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {10, 20},
+ /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {4, 5},
+ /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1))
+ .compose();
+ long[] expectedPattern = new long[] {7, 33, 4, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_composition_withOffDuration() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addOffDuration(Duration.ofMillis(20))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {10, 20},
+ /* amplitudes= */ new int[] {0, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {30, 40},
+ /* amplitudes= */ new int[] {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE},
+ /* repeatIndex= */ -1))
+ .addOffDuration(Duration.ofMillis(10))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {4, 5},
+ /* repeatIndex= */ -1))
+ .addOffDuration(Duration.ofMillis(5))
+ .compose();
+ long[] expectedPattern = new long[] {30, 90, 14, 5, 5};
+
+ assertArrayEq(expectedPattern, effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_composition_withPrimitives() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK)
+ .addOffDuration(Duration.ofMillis(20))
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {5},
+ /* repeatIndex= */ -1))
+ .compose();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_composition_repeating() {
+ VibrationEffect effect = VibrationEffect.startComposition()
+ .addEffect(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {5},
+ /* repeatIndex= */ -1))
+ .repeatEffectIndefinitely(
+ VibrationEffect.createWaveform(
+ /* timings= */ new long[] {2, 3},
+ /* repeatIndex= */ -1))
+ .compose();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
+ public void computeLegacyPattern_effectsViaStartWaveform() {
+ // Effects created via startWaveform are not expected to be converted to long[] patterns, as
+ // they are not configured to always play with the default amplitude.
+ VibrationEffect effect = VibrationEffect.startWaveform(targetFrequency(60))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(1), targetFrequency(120))
+ .addSustain(Duration.ofMillis(200))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(0), targetFrequency(60))
+ .build();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.startWaveform(targetFrequency(60))
+ .addTransition(Duration.ofMillis(80), targetAmplitude(1))
+ .addSustain(Duration.ofMillis(200))
+ .addTransition(Duration.ofMillis(100), targetAmplitude(0))
+ .build();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+ effect = VibrationEffect.startWaveform(targetFrequency(60))
+ .addTransition(Duration.ofMillis(100), targetFrequency(50))
+ .addSustain(Duration.ofMillis(50))
+ .addTransition(Duration.ofMillis(20), targetFrequency(75))
+ .build();
+
+ assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+ }
+
+ @Test
public void getRingtones_noPrebakedRingtones() {
Resources r = mockRingtoneResources(new String[0]);
Context context = mockContext(r);
@@ -100,7 +449,7 @@
@Test
public void testValidateOneShot() {
VibrationEffect.createOneShot(1, 255).validate();
- VibrationEffect.createOneShot(1, VibrationEffect.DEFAULT_AMPLITUDE).validate();
+ VibrationEffect.createOneShot(1, DEFAULT_AMPLITUDE).validate();
assertThrows(IllegalArgumentException.class,
() -> VibrationEffect.createOneShot(-1, 255).validate());
@@ -501,6 +850,13 @@
assertTrue(VibrationEffect.get(VibrationEffect.EFFECT_TICK).isHapticFeedbackCandidate());
}
+ private void assertArrayEq(long[] expected, long[] actual) {
+ assertTrue(
+ String.format("Expected pattern %s, but was %s",
+ Arrays.toString(expected), Arrays.toString(actual)),
+ Arrays.equals(expected, actual));
+ }
+
private Resources mockRingtoneResources() {
return mockRingtoneResources(new String[]{
RINGTONE_URI_1,
diff --git a/core/tests/coretests/src/android/view/DisplayShapeTest.java b/core/tests/coretests/src/android/view/DisplayShapeTest.java
new file mode 100644
index 0000000..77dd8bd
--- /dev/null
+++ b/core/tests/coretests/src/android/view/DisplayShapeTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link DisplayShape}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class DisplayShapeTest {
+ // Rectangle with w=100, height=200
+ private static final String SPEC_RECTANGULAR_SHAPE = "M0,0 L100,0 L100,200 L0,200 Z";
+
+ @Test
+ public void testGetPath() {
+ final DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 100f, 200f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testDefaultShape_screenIsRound() {
+ final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 100, true);
+
+ // A circle with radius = 50.
+ final String expect = "M0,50.0 A50.0,50.0 0 1,1 100,50.0 A50.0,50.0 0 1,1 0,50.0 Z";
+ assertEquals(displayShape.mDisplayShapeSpec, expect);
+ }
+
+ @Test
+ public void testDefaultShape_screenIsNotRound() {
+ final DisplayShape displayShape = DisplayShape.createDefaultDisplayShape(100, 200, false);
+
+ // A rectangle with width/height = 100/200.
+ final String expect = "M0,0 L100,0 L100,200 L0,200 Z";
+ assertEquals(displayShape.mDisplayShapeSpec, expect);
+ }
+
+ @Test
+ public void testFromSpecString_cache() {
+ final DisplayShape cached = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ assertThat(DisplayShape.fromSpecString(SPEC_RECTANGULAR_SHAPE, 1f, 100, 200),
+ sameInstance(cached));
+ }
+
+ @Test
+ public void testGetPath_cache() {
+ final Path cached = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath();
+ assertThat(DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200).getPath(),
+ sameInstance(cached));
+ }
+
+ @Test
+ public void testRotate_90() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setRotation(ROTATION_90);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testRotate_270() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setRotation(ROTATION_270);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 200f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testOffset() {
+ DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 1f, 100, 200);
+ displayShape = displayShape.setOffset(-10, -20);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(-10f, -20f, 90f, 180f);
+ assertEquals(actualRect, expectRect);
+ }
+
+ @Test
+ public void testPhysicalPixelDisplaySizeRatio() {
+ final DisplayShape displayShape = DisplayShape.fromSpecString(
+ SPEC_RECTANGULAR_SHAPE, 0.5f, 100, 200);
+ final Path path = displayShape.getPath();
+ final RectF actualRect = new RectF();
+ path.computeBounds(actualRect, false);
+
+ final RectF expectRect = new RectF(0f, 0f, 50f, 100f);
+ assertEquals(actualRect, expectRect);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index be9da11..6a96f28 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -517,6 +517,19 @@
windowInsets.getRoundedCorner(POSITION_BOTTOM_LEFT));
}
+ @Test
+ public void testCalculateRelativeDisplayShape() {
+ mState.setDisplayFrame(new Rect(0, 0, 200, 400));
+ mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false));
+ WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false,
+ false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION,
+ WINDOWING_MODE_UNDEFINED, new SparseIntArray());
+
+ final DisplayShape expect =
+ DisplayShape.createDefaultDisplayShape(200, 400, false).setOffset(-10, -20);
+ assertEquals(expect, windowInsets.getDisplayShape());
+ }
+
private void assertEqualsAndHashCode() {
assertEquals(mState, mState2);
assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index dd9634b..4fed396 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -39,13 +39,16 @@
@Test
public void systemWindowInsets_afterConsuming_isConsumed() {
- assertTrue(new WindowInsets(new Rect(1, 2, 3, 4), null, false, false, null)
+ assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null,
+ null, false, false, null, null, null, null,
+ WindowInsets.Type.systemBars(), false)
.consumeSystemWindowInsets().isConsumed());
}
@Test
public void multiNullConstructor_isConsumed() {
- assertTrue(new WindowInsets((Rect) null, null, false, false, null).isConsumed());
+ assertTrue(new WindowInsets(null, null, null, false, false, null, null, null, null,
+ WindowInsets.Type.systemBars(), false).isConsumed());
}
@Test
@@ -61,7 +64,8 @@
WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0));
WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0));
WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null,
- null, null, systemBars(), true /* compatIgnoreVisibility */);
+ null, null, DisplayShape.NONE, systemBars(),
+ true /* compatIgnoreVisibility */);
assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets());
}
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
index d10f173..4d4ec35 100644
--- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java
@@ -168,7 +168,8 @@
}
private WindowInsets insetsWith(Insets content, DisplayCutout cutout) {
- return new WindowInsets(content.toRect(), null, false, false, cutout);
+ return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null,
+ false, false, cutout, null, null, null, WindowInsets.Type.systemBars(), false);
}
private ViewGroup createViewGroupWithId(int id) {
diff --git a/core/tests/utiltests/src/android/util/IntArrayTest.java b/core/tests/utiltests/src/android/util/IntArrayTest.java
index a76c640..caa7312 100644
--- a/core/tests/utiltests/src/android/util/IntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/IntArrayTest.java
@@ -16,8 +16,8 @@
package android.util;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -25,6 +25,8 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class IntArrayTest {
@@ -35,51 +37,65 @@
a.add(1);
a.add(2);
a.add(3);
- verify(new int[]{1, 2, 3}, a);
+ verify(a, 1, 2, 3);
IntArray b = IntArray.fromArray(new int[]{4, 5, 6, 7, 8}, 3);
a.addAll(b);
- verify(new int[]{1, 2, 3, 4, 5, 6}, a);
+ verify(a, 1, 2, 3, 4, 5, 6);
a.resize(2);
- verify(new int[]{1, 2}, a);
+ verify(a, 1, 2);
a.resize(8);
- verify(new int[]{1, 2, 0, 0, 0, 0, 0, 0}, a);
+ verify(a, 1, 2, 0, 0, 0, 0, 0, 0);
a.set(5, 10);
- verify(new int[]{1, 2, 0, 0, 0, 10, 0, 0}, a);
+ verify(a, 1, 2, 0, 0, 0, 10, 0, 0);
a.add(5, 20);
- assertEquals(20, a.get(5));
- assertEquals(5, a.indexOf(20));
- verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0}, a);
+ assertThat(a.get(5)).isEqualTo(20);
+ assertThat(a.indexOf(20)).isEqualTo(5);
+ verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0);
- assertEquals(-1, a.indexOf(99));
+ assertThat(a.indexOf(99)).isEqualTo(-1);
a.resize(15);
a.set(14, 30);
- verify(new int[]{1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30}, a);
+ verify(a, 1, 2, 0, 0, 0, 20, 10, 0, 0, 0, 0, 0, 0, 0, 30);
int[] backingArray = new int[]{1, 2, 3, 4};
a = IntArray.wrap(backingArray);
a.set(0, 10);
- assertEquals(10, backingArray[0]);
+ assertThat(backingArray[0]).isEqualTo(10);
backingArray[1] = 20;
backingArray[2] = 30;
- verify(backingArray, a);
- assertEquals(2, a.indexOf(30));
+ verify(a, backingArray);
+ assertThat(a.indexOf(30)).isEqualTo(2);
a.resize(2);
- assertEquals(0, backingArray[2]);
- assertEquals(0, backingArray[3]);
+ assertThat(backingArray[2]).isEqualTo(0);
+ assertThat(backingArray[3]).isEqualTo(0);
a.add(50);
- verify(new int[]{10, 20, 50}, a);
+ verify(a, 10, 20, 50);
}
- public void verify(int[] expected, IntArray intArray) {
- assertEquals(expected.length, intArray.size());
- assertArrayEquals(expected, intArray.toArray());
+ @Test
+ public void testToString() {
+ IntArray a = new IntArray(10);
+ a.add(4);
+ a.add(8);
+ a.add(15);
+ a.add(16);
+ a.add(23);
+ a.add(42);
+
+ assertWithMessage("toString()").that(a.toString()).contains("4, 8, 15, 16, 23, 42");
+ assertWithMessage("toString()").that(a.toString()).doesNotContain("0");
+ }
+
+ public void verify(IntArray intArray, int... expected) {
+ assertWithMessage("contents of %s", intArray).that(intArray.toArray()).asList()
+ .containsExactlyElementsIn(Arrays.stream(expected).boxed().toList());
}
}
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 215b60e..a43e225 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -371,6 +371,10 @@
# key 413 "KEY_DIGITS"
# key 414 "KEY_TEEN"
# key 415 "KEY_TWEN"
+# key 418 "KEY_ZOOM_IN"
+key 418 ZOOM_IN
+# key 419 "KEY_ZOOM_OUT"
+key 419 ZOOM_OUT
key 528 FOCUS
key 429 CONTACTS
diff --git a/graphics/java/android/graphics/PathIterator.java b/graphics/java/android/graphics/PathIterator.java
index 33b9a47..bfda690 100644
--- a/graphics/java/android/graphics/PathIterator.java
+++ b/graphics/java/android/graphics/PathIterator.java
@@ -281,7 +281,7 @@
return mConicWeight;
}
- public Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
+ Segment(@NonNull @Verb int verb, @NonNull float[] points, float conicWeight) {
mVerb = verb;
mPoints = points;
mConicWeight = conicWeight;
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 3b7d0e1..9fb627f 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -1207,6 +1207,7 @@
* It is safe to call this method twice or more on the same instance.
* @hide
*/
+ @TestApi
public void releaseNativeObjectForTest() {
mCleaner.run();
}
@@ -1294,6 +1295,13 @@
/**
* Deserialize the font mapping from the serialized byte buffer.
*
+ * <p>Warning: the given {@code buffer} must outlive generated Typeface
+ * objects in {@code out}. In production code, this is guaranteed by
+ * storing the buffer in {@link #sSystemFontMapBuffer}.
+ * If you call this method in a test, please make sure to destroy the
+ * generated Typeface objects by calling
+ * {@link #releaseNativeObjectForTest()}.
+ *
* @hide
*/
@TestApi
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 82ced43..0e198d5 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -382,9 +382,9 @@
}
/**
- * Creates a PixelCopy request for the given {@link Window}
+ * Creates a PixelCopy Builder for the given {@link Window}
* @param source The Window to copy from
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofWindow(@NonNull Window source) {
@@ -394,7 +394,7 @@
}
/**
- * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+ * Creates a PixelCopy Builder for the {@link Window} that the given {@link View} is
* attached to.
*
* Note that this copy request is not cropped to the area the View occupies by default.
@@ -404,7 +404,7 @@
*
* @param source A View that {@link View#isAttachedToWindow() is attached} to a window
* that will be used to retrieve the window to copy from.
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofWindow(@NonNull View source) {
@@ -427,10 +427,10 @@
}
/**
- * Creates a PixelCopy request for the given {@link Surface}
+ * Creates a PixelCopy Builder for the given {@link Surface}
*
* @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofSurface(@NonNull Surface source) {
@@ -441,12 +441,12 @@
}
/**
- * Creates a PixelCopy request for the {@link Surface} belonging to the
+ * Creates a PixelCopy Builder for the {@link Surface} belonging to the
* given {@link SurfaceView}
*
* @param source The SurfaceView to copy from. The backing surface must be
* {@link Surface#isValid() valid}
- * @return A {@link Builder} builder to set the optional params & execute the request
+ * @return A {@link Builder} builder to set the optional params & build the request
*/
@SuppressLint("BuilderSetStyle")
public static @NonNull Builder ofSurface(@NonNull SurfaceView source) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index a0dde6a..2ec9e8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.animation;
+import android.graphics.Path;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
@@ -53,6 +54,11 @@
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
/**
+ * The default emphasized interpolator. Used for hero / emphasized movement of content.
+ */
+ public static final Interpolator EMPHASIZED = createEmphasizedInterpolator();
+
+ /**
* The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
* is disappearing e.g. when moving off screen.
*/
@@ -81,4 +87,14 @@
public static final PathInterpolator DIM_INTERPOLATOR =
new PathInterpolator(.23f, .87f, .52f, -0.11f);
+
+ // Create the default emphasized interpolator
+ private static PathInterpolator createEmphasizedInterpolator() {
+ Path path = new Path();
+ // Doing the same as fast_out_extra_slow_in
+ path.moveTo(0f, 0f);
+ path.cubicTo(0.05f, 0f, 0.133333f, 0.06f, 0.166666f, 0.4f);
+ path.cubicTo(0.208333f, 0.82f, 0.25f, 1f, 1f, 1f);
+ return new PathInterpolator(path);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
new file mode 100644
index 0000000..36cf29a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.back;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.annotation.NonNull;
+import android.graphics.Color;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+
+/**
+ * Controls background surface for the back animations
+ */
+public class BackAnimationBackground {
+ private static final int BACKGROUND_LAYER = -1;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private SurfaceControl mBackgroundSurface;
+
+ public BackAnimationBackground(RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ void ensureBackground(int color, @NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface != null) {
+ return;
+ }
+
+ final float[] colorComponents = new float[] { Color.red(color) / 255.f,
+ Color.green(color) / 255.f, Color.blue(color) / 255.f };
+
+ final SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
+ .setName("back-animation-background")
+ .setCallsite("BackAnimationBackground")
+ .setColorLayer();
+
+ mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
+ mBackgroundSurface = colorLayerBuilder.build();
+ transaction.setColor(mBackgroundSurface, colorComponents)
+ .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
+ .show(mBackgroundSurface);
+ }
+
+ void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+ if (mBackgroundSurface == null) {
+ return;
+ }
+
+ if (mBackgroundSurface.isValid()) {
+ transaction.remove(mBackgroundSurface);
+ }
+ mBackgroundSurface = null;
+ }
+}
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 f811940..0133f6b 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
@@ -138,14 +138,18 @@
}
};
+ private final BackAnimationBackground mAnimationBackground;
+
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
- Context context) {
+ Context context,
+ @NonNull BackAnimationBackground backAnimationBackground) {
this(shellInit, shellController, shellExecutor, backgroundHandler,
- ActivityTaskManager.getService(), context, context.getContentResolver());
+ ActivityTaskManager.getService(), context, context.getContentResolver(),
+ backAnimationBackground);
}
@VisibleForTesting
@@ -155,7 +159,8 @@
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
- Context context, ContentResolver contentResolver) {
+ Context context, ContentResolver contentResolver,
+ @NonNull BackAnimationBackground backAnimationBackground) {
mShellController = shellController;
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
@@ -163,6 +168,7 @@
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
+ mAnimationBackground = backAnimationBackground;
}
@VisibleForTesting
@@ -184,10 +190,14 @@
return;
}
- final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+ final CrossTaskBackAnimation crossTaskAnimation =
+ new CrossTaskBackAnimation(mContext, mAnimationBackground);
mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
- new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
- // TODO (238474994): register cross activity animation when it's completed.
+ crossTaskAnimation.mBackAnimationRunner);
+ final CrossActivityAnimation crossActivityAnimation =
+ new CrossActivityAnimation(mContext, mAnimationBackground);
+ mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+ crossActivityAnimation.mBackAnimationRunner);
// TODO (236760237): register dialog close animation when it's completed.
}
@@ -275,7 +285,8 @@
@Override
public void clearBackToLauncherCallback() {
executeRemoteCallWithTaskPermission(mController, "clearBackToLauncherCallback",
- (controller) -> controller.clearBackToLauncherCallback());
+ (controller) -> controller.unregisterAnimation(
+ BackNavigationInfo.TYPE_RETURN_TO_HOME));
}
@Override
@@ -289,8 +300,8 @@
mAnimationDefinition.set(type, runner);
}
- private void clearBackToLauncherCallback() {
- mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+ mAnimationDefinition.remove(type);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
new file mode 100644
index 0000000..9f6bc5d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -0,0 +1,373 @@
+/*
+ * 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.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/** Class that defines cross-activity animation. */
+@ShellMainThread
+class CrossActivityAnimation {
+ /**
+ * Minimum scale of the entering/closing window.
+ */
+ private static final float MIN_WINDOW_SCALE = 0.9f;
+
+ /**
+ * Minimum alpha of the closing/entering window.
+ */
+ private static final float CLOSING_MIN_WINDOW_ALPHA = 0.5f;
+
+ /**
+ * Progress value to fly out closing window and fly in entering window.
+ */
+ private static final float SWITCH_ENTERING_WINDOW_PROGRESS = 0.5f;
+
+ /** Max window translation in the Y axis. */
+ private static final int WINDOW_MAX_DELTA_Y = 160;
+
+ /** Duration of fade in/out entering window. */
+ private static final int FADE_IN_DURATION = 100;
+ /** Duration of post animation after gesture committed. */
+ private static final int POST_ANIMATION_DURATION = 350;
+ private static final Interpolator INTERPOLATOR = Interpolators.EMPHASIZED;
+
+ private final Rect mStartTaskRect = new Rect();
+ private final float mCornerRadius;
+
+ // The closing window properties.
+ private final RectF mClosingRect = new RectF();
+
+ // The entering window properties.
+ private final Rect mEnteringStartRect = new Rect();
+ private final RectF mEnteringRect = new RectF();
+
+ private float mCurrentAlpha = 1.0f;
+
+ private float mEnteringMargin = 0;
+ private ValueAnimator mEnteringAnimator;
+ private boolean mEnteringWindowShow = false;
+
+ private final PointF mInitialTouchPos = new PointF();
+
+ private final Matrix mTransformMatrix = new Matrix();
+
+ private final float[] mTmpFloat9 = new float[9];
+
+ private RemoteAnimationTarget mEnteringTarget;
+ private RemoteAnimationTarget mClosingTarget;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+ private boolean mBackInProgress = false;
+
+ private PointF mTouchPos = new PointF();
+ private IRemoteAnimationFinishedCallback mFinishCallback;
+
+ private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
+
+ private final BackAnimationBackground mBackground;
+
+ CrossActivityAnimation(Context context, BackAnimationBackground background) {
+ mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
+ }
+
+ private static float mapRange(float value, float min, float max) {
+ return min + (value * (max - min));
+ }
+
+ private float getInterpolatedProgress(float backProgress) {
+ return INTERPOLATOR.getInterpolation(backProgress);
+ }
+
+ private void startBackAnimation() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+ return;
+ }
+ mTransaction.setAnimationTransaction();
+
+ // Offset start rectangle to align task bounds.
+ mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+ mStartTaskRect.offsetTo(0, 0);
+
+ // Draw background with task background color.
+ mBackground.ensureBackground(
+ mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+ }
+
+ private void applyTransform(SurfaceControl leash, RectF targetRect, float targetAlpha) {
+ final float scale = targetRect.width() / mStartTaskRect.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+ mTransaction.setAlpha(leash, targetAlpha)
+ .setMatrix(leash, mTransformMatrix, mTmpFloat9)
+ .setWindowCrop(leash, mStartTaskRect)
+ .setCornerRadius(leash, mCornerRadius);
+ }
+
+ private void finishAnimation() {
+ if (mEnteringTarget != null) {
+ mEnteringTarget.leash.release();
+ mEnteringTarget = null;
+ }
+ if (mClosingTarget != null) {
+ mClosingTarget.leash.release();
+ mClosingTarget = null;
+ }
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
+ }
+
+ mTransaction.apply();
+ mBackInProgress = false;
+ mTransformMatrix.reset();
+ mInitialTouchPos.set(0, 0);
+ mEnteringWindowShow = false;
+ mEnteringMargin = 0;
+
+ if (mFinishCallback != null) {
+ try {
+ mFinishCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ mFinishCallback = null;
+ }
+ }
+
+ private void onGestureProgress(@NonNull BackEvent backEvent) {
+ if (!mBackInProgress) {
+ mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+ mBackInProgress = true;
+ }
+ mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ return;
+ }
+
+ final float progress = getInterpolatedProgress(backEvent.getProgress());
+ final float touchY = mTouchPos.y;
+
+ final int width = mStartTaskRect.width();
+ final int height = mStartTaskRect.height();
+
+ final float closingScale = mapRange(progress, 1, MIN_WINDOW_SCALE);
+
+ final float closingWidth = closingScale * width;
+ final float closingHeight = (float) height / width * closingWidth;
+
+ // Move the window along the X axis.
+ final float closingLeft = mStartTaskRect.left + (width - closingWidth) / 2;
+
+ // Move the window along the Y axis.
+ final float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+ final float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+ final float closingTop = (height - closingHeight) * 0.5f + deltaY;
+ mClosingRect.set(
+ closingLeft, closingTop, closingLeft + closingWidth, closingTop + closingHeight);
+ mEnteringRect.set(mClosingRect);
+
+ // Switch closing/entering targets while reach to the threshold progress.
+ if (showEnteringWindow(progress > SWITCH_ENTERING_WINDOW_PROGRESS)) {
+ return;
+ }
+
+ // Present windows and update the alpha.
+ mCurrentAlpha = Math.max(mapRange(progress, 1.0f, 0), CLOSING_MIN_WINDOW_ALPHA);
+ mClosingRect.offset(mEnteringMargin, 0);
+ mEnteringRect.offset(mEnteringMargin - width, 0);
+
+ applyTransform(
+ mClosingTarget.leash, mClosingRect, mEnteringWindowShow ? 0.01f : mCurrentAlpha);
+ applyTransform(
+ mEnteringTarget.leash, mEnteringRect, mEnteringWindowShow ? mCurrentAlpha : 0.01f);
+ mTransaction.apply();
+ }
+
+ private boolean showEnteringWindow(boolean show) {
+ if (mEnteringAnimator == null) {
+ mEnteringAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(FADE_IN_DURATION);
+ mEnteringAnimator.setInterpolator(new AccelerateInterpolator());
+ mEnteringAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ final int width = mStartTaskRect.width();
+ mEnteringMargin = width * progress;
+ // We don't animate to 0 or the surface would become invisible and lose focus.
+ final float alpha = progress >= 0.5f ? 0.01f
+ : mapRange(progress * 2, mCurrentAlpha, 0.01f);
+ mClosingRect.offset(mEnteringMargin, 0);
+ mEnteringRect.offset(mEnteringMargin - width, 0);
+
+ applyTransform(mClosingTarget.leash, mClosingRect, alpha);
+ applyTransform(mEnteringTarget.leash, mEnteringRect, mCurrentAlpha);
+ mTransaction.apply();
+ });
+ }
+
+ if (mEnteringAnimator.isRunning()) {
+ return true;
+ }
+
+ if (mEnteringWindowShow == show) {
+ return false;
+ }
+
+ mEnteringWindowShow = show;
+ if (show) {
+ mEnteringAnimator.start();
+ } else {
+ mEnteringAnimator.reverse();
+ }
+ return true;
+ }
+
+ private void onGestureCommitted() {
+ if (mEnteringTarget == null || mClosingTarget == null) {
+ finishAnimation();
+ return;
+ }
+
+ // End the fade in animation.
+ if (mEnteringAnimator.isRunning()) {
+ mEnteringAnimator.cancel();
+ }
+
+ // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+ // coordinate of the gesture driven phase.
+ mEnteringRect.round(mEnteringStartRect);
+ mTransaction.hide(mClosingTarget.leash);
+
+ ValueAnimator valueAnimator =
+ ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION);
+ valueAnimator.setInterpolator(new DecelerateInterpolator());
+ valueAnimator.addUpdateListener(animation -> {
+ float progress = animation.getAnimatedFraction();
+ updatePostCommitEnteringAnimation(progress);
+ mTransaction.apply();
+ });
+
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ valueAnimator.start();
+ }
+
+ private void updatePostCommitEnteringAnimation(float progress) {
+ float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+ float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+ float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+ float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+ float alpha = mapRange(progress, mCurrentAlpha, 1.0f);
+
+ mEnteringRect.set(left, top, left + width, top + height);
+ applyTransform(mEnteringTarget.leash, mEnteringRect, alpha);
+ }
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossActivityAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ // End the fade in animation.
+ if (mEnteringAnimator.isRunning()) {
+ mEnteringAnimator.cancel();
+ }
+ // TODO (b259608500): Let BackProgressAnimator could play cancel animation.
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ }
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(
+ int transit,
+ RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to activity animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+
+ @Override
+ public void onAnimationCancelled(boolean isKeyguardOccluded) {
+ finishAnimation();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index 2074b6a..a9a7b77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -61,7 +61,7 @@
*/
@ShellMainThread
class CrossTaskBackAnimation {
- private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+ private static final int BACKGROUNDCOLOR = 0x43433A;
/**
* Minimum scale of the entering window.
@@ -106,7 +106,6 @@
private RemoteAnimationTarget mEnteringTarget;
private RemoteAnimationTarget mClosingTarget;
- private SurfaceControl mBackgroundSurface;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mBackInProgress = false;
@@ -115,56 +114,15 @@
private float mProgress = 0;
private PointF mTouchPos = new PointF();
private IRemoteAnimationFinishedCallback mFinishCallback;
-
private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+ final BackAnimationRunner mBackAnimationRunner;
- final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
- @Override
- public void onBackStarted(BackEvent backEvent) {
- mProgressAnimator.onBackStarted(backEvent,
- CrossTaskBackAnimation.this::onGestureProgress);
- }
+ private final BackAnimationBackground mBackground;
- @Override
- public void onBackProgressed(@NonNull BackEvent backEvent) {
- mProgressAnimator.onBackProgressed(backEvent);
- }
-
- @Override
- public void onBackCancelled() {
- mProgressAnimator.reset();
- finishAnimation();
- }
-
- @Override
- public void onBackInvoked() {
- mProgressAnimator.reset();
- onGestureCommitted();
- }
- };
-
- final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
- for (RemoteAnimationTarget a : apps) {
- if (a.mode == MODE_CLOSING) {
- mClosingTarget = a;
- }
- if (a.mode == MODE_OPENING) {
- mEnteringTarget = a;
- }
- }
-
- startBackAnimation();
- mFinishCallback = finishedCallback;
- }
- };
-
- CrossTaskBackAnimation(Context context) {
+ CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+ mBackground = background;
}
private float getInterpolatedProgress(float backProgress) {
@@ -182,14 +140,7 @@
mStartTaskRect.offsetTo(0, 0);
// Draw background.
- mBackgroundSurface = new SurfaceControl.Builder()
- .setName("Background of Back Navigation")
- .setColorLayer()
- .setHidden(false)
- .build();
- mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
- .setLayer(mBackgroundSurface, -1);
- mTransaction.apply();
+ mBackground.ensureBackground(BACKGROUNDCOLOR, mTransaction);
}
private void updateGestureBackProgress(float progress, BackEvent event) {
@@ -300,11 +251,11 @@
mClosingTarget = null;
}
- if (mBackgroundSurface != null) {
- mBackgroundSurface.release();
- mBackgroundSurface = null;
+ if (mBackground != null) {
+ mBackground.removeBackground(mTransaction);
}
+ mTransaction.apply();
mBackInProgress = false;
mTransformMatrix.reset();
mClosingCurrentRect.setEmpty();
@@ -362,4 +313,49 @@
private static float mapRange(float value, float min, float max) {
return min + (value * (max - min));
}
+
+ private final class Callback extends IOnBackInvokedCallback.Default {
+ @Override
+ public void onBackStarted(BackEvent backEvent) {
+ mProgressAnimator.onBackStarted(backEvent,
+ CrossTaskBackAnimation.this::onGestureProgress);
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ mProgressAnimator.onBackProgressed(backEvent);
+ }
+
+ @Override
+ public void onBackCancelled() {
+ mProgressAnimator.reset();
+ finishAnimation();
+ }
+
+ @Override
+ public void onBackInvoked() {
+ mProgressAnimator.reset();
+ onGestureCommitted();
+ }
+ };
+
+ private final class Runner extends IRemoteAnimationRunner.Default {
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+ for (RemoteAnimationTarget a : apps) {
+ if (a.mode == MODE_CLOSING) {
+ mClosingTarget = a;
+ }
+ if (a.mode == MODE_OPENING) {
+ mEnteringTarget = a;
+ }
+ }
+
+ startBackAnimation();
+ mFinishCallback = finishedCallback;
+ }
+ };
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 962be9d..4ea8a5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.back.BackAnimationBackground;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -93,13 +94,13 @@
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import com.android.wm.shell.windowdecor.WindowDecorViewModel;
-import java.util.Optional;
-
import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+
/**
* Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
* accessible from components within the WM subcomponent (can be explicitly exposed to the
@@ -255,21 +256,30 @@
@WMSingleton
@Provides
+ static BackAnimationBackground provideBackAnimationBackground(
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ return new BackAnimationBackground(rootTaskDisplayAreaOrganizer);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<BackAnimationController> provideBackAnimationController(
Context context,
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler
+ @ShellBackgroundThread Handler backgroundHandler,
+ BackAnimationBackground backAnimationBackground
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellController, shellExecutor,
- backgroundHandler, context));
+ backgroundHandler, context, backAnimationBackground));
}
return Optional.empty();
}
+
//
// Bubbles (optional feature)
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..8ba2583 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -63,6 +63,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
+import android.view.Choreographer;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -179,8 +180,10 @@
// This is necessary in case there was a resize animation ongoing when exit PIP
// started, in which case the first resize will be skipped to let the exit
// operation handle the final resize out of PIP mode. See b/185306679.
- finishResize(tx, destinationBounds, direction, animationType);
- sendOnPipTransitionFinished(direction);
+ finishResizeDelayedIfNeeded(() -> {
+ finishResize(tx, destinationBounds, direction, animationType);
+ sendOnPipTransitionFinished(direction);
+ });
}
}
@@ -196,6 +199,39 @@
}
};
+ /**
+ * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+ *
+ * This is done to avoid a race condition between the last transaction applied in
+ * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
+ * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
+ * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
+ * the WCT should be the last transaction to finish the animation. However, it may happen that
+ * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
+ * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
+ * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
+ * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
+ * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
+ *
+ * To avoid this, we delay the finishResize operation until
+ * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
+ */
+ private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+ if (!shouldSyncPipTransactionWithMenu()) {
+ finishResizeRunnable.run();
+ return;
+ }
+
+ // Delay the finishResize to the next frame
+ Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
+ mMainExecutor.execute(finishResizeRunnable);
+ }, null);
+ }
+
+ private boolean shouldSyncPipTransactionWithMenu() {
+ return mPipMenuController.isMenuVisible();
+ }
+
@VisibleForTesting
final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +257,7 @@
@Override
public boolean handlePipTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, Rect destinationBounds) {
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(leash, tx, destinationBounds);
return true;
}
@@ -1223,7 +1259,7 @@
mSurfaceTransactionHelper
.crop(tx, mLeash, toBounds)
.round(tx, mLeash, mPipTransitionState.isInPip());
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
@@ -1265,7 +1301,7 @@
mSurfaceTransactionHelper
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
- if (mPipMenuController.isMenuVisible()) {
+ if (shouldSyncPipTransactionWithMenu()) {
mPipMenuController.movePipMenu(mLeash, tx, toBounds);
} else {
tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e1fa29..485b400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -77,10 +77,10 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
}
@@ -90,7 +90,13 @@
if (mRemote.asBinder() != null) {
mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */);
}
- mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT = RemoteTransitionHandler.copyIfLocal(
+ startTransaction, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -124,7 +130,13 @@
}
};
try {
- mRemote.getRemoteTransition().mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT =
+ RemoteTransitionHandler.copyIfLocal(t, mRemote.getRemoteTransition());
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ mRemote.getRemoteTransition().mergeAnimation(
+ transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error merging remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index 9469529..b4e0584 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -120,10 +121,10 @@
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
unhandleDeath(remote.asBinder(), finishCallback);
+ if (sct != null) {
+ finishTransaction.merge(sct);
+ }
mMainExecutor.execute(() -> {
- if (sct != null) {
- finishTransaction.merge(sct);
- }
mRequestedRemotes.remove(transition);
finishCallback.onTransitionFinished(wct, null /* wctCB */);
});
@@ -131,8 +132,14 @@
};
Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
try {
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteStartT =
+ copyIfLocal(startTransaction, remote.getRemoteTransition());
+ final TransitionInfo remoteInfo =
+ remoteStartT == startTransaction ? info : info.localRemoteCopy();
handleDeath(remote.asBinder(), finishCallback);
- remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
// assume that remote will apply the start transaction.
startTransaction.clear();
} catch (RemoteException e) {
@@ -145,6 +152,28 @@
return true;
}
+ static SurfaceControl.Transaction copyIfLocal(SurfaceControl.Transaction t,
+ IRemoteTransition remote) {
+ // We care more about parceling than local (though they should be the same); so, use
+ // queryLocalInterface since that's what Binder uses to decide if it needs to parcel.
+ if (remote.asBinder().queryLocalInterface(IRemoteTransition.DESCRIPTOR) == null) {
+ // No local interface, so binder itself will parcel and thus we don't need to.
+ return t;
+ }
+ // Binder won't be parceling; however, the remotes assume they have their own native
+ // objects (and don't know if caller is local or not), so we need to make a COPY here so
+ // that the remote can clean it up without clearing the original transaction.
+ // Since there's no direct `copy` for Transaction, we have to parcel/unparcel instead.
+ final Parcel p = Parcel.obtain();
+ try {
+ t.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ return SurfaceControl.Transaction.CREATOR.createFromParcel(p);
+ } finally {
+ p.recycle();
+ }
+ }
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@@ -175,7 +204,11 @@
}
};
try {
- remote.mergeAnimation(transition, info, t, mergeTarget, cb);
+ // If the remote is actually in the same process, then make a copy of parameters since
+ // remote impls assume that they have to clean-up native references.
+ final SurfaceControl.Transaction remoteT = copyIfLocal(t, remote);
+ final TransitionInfo remoteInfo = remoteT == t ? info : info.localRemoteCopy();
+ remote.mergeAnimation(transition, remoteInfo, remoteT, mergeTarget, cb);
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 56d51bd..c6935c0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -503,6 +503,7 @@
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
+ releaseSurfaces(info);
return;
}
@@ -607,6 +608,15 @@
onFinish(transition, wct, wctCB, false /* abort */);
}
+ /**
+ * Releases an info's animation-surfaces. These don't need to persist and we need to release
+ * them asap so that SF can free memory sooner.
+ */
+ private void releaseSurfaces(@Nullable TransitionInfo info) {
+ if (info == null) return;
+ info.releaseAnimSurfaces();
+ }
+
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB,
@@ -645,6 +655,11 @@
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
+ if (active.mStartT != null) {
+ // Applied by now, so close immediately. Do not set to null yet, though, since nullness
+ // is used later to disambiguate malformed transitions.
+ active.mStartT.close();
+ }
// Merge all relevant transactions together
SurfaceControl.Transaction fullFinish = active.mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
@@ -664,12 +679,14 @@
fullFinish.apply();
}
// Now perform all the finishes.
+ releaseSurfaces(active.mInfo);
mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(transition, wct, wctCB);
while (activeIdx < mActiveTransitions.size()) {
if (!mActiveTransitions.get(activeIdx).mMerged) break;
ActiveTransition merged = mActiveTransitions.remove(activeIdx);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+ releaseSurfaces(merged.mInfo);
}
// sift through aborted transitions
while (mActiveTransitions.size() > activeIdx
@@ -682,8 +699,9 @@
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
- mObservers.get(i).onTransitionFinished(active.mToken, true);
+ mObservers.get(i).onTransitionFinished(aborted.mToken, true);
}
+ releaseSurfaces(aborted.mInfo);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index d75c36c..bee9a90 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -110,6 +110,9 @@
@Mock
private ShellController mShellController;
+ @Mock
+ private BackAnimationBackground mAnimationBackground;
+
private BackAnimationController mController;
private TestableContentResolver mContentResolver;
private TestableLooper mTestableLooper;
@@ -127,7 +130,7 @@
mController = new BackAnimationController(mShellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
mController.setEnableUAnimation(true);
mShellInit.init();
mShellExecutor.flushAll();
@@ -239,7 +242,7 @@
mController = new BackAnimationController(shellInit, mShellController,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
- mContentResolver);
+ mContentResolver, mAnimationBackground);
shellInit.init();
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
@@ -354,6 +357,10 @@
BackNavigationInfo.TYPE_DIALOG_CLOSE};
for (int type: testTypes) {
+ unregisterAnimation(type);
+ }
+
+ for (int type: testTypes) {
final ResultListener result = new ResultListener();
createNavigationInfo(new BackNavigationInfo.Builder()
.setType(type)
@@ -431,6 +438,10 @@
new BackAnimationRunner(mAnimatorCallback, mBackAnimationRunner));
}
+ private void unregisterAnimation(int type) {
+ mController.unregisterAnimation(type);
+ }
+
private static class ResultListener implements RemoteCallback.OnResultListener {
boolean mBackNavigationDone = false;
boolean mTriggerBack = false;
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
old mode 100755
new mode 100644
index 9aa3787..15aaae2
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -18,6 +18,7 @@
#include "android-base/errors.h"
#include "android-base/logging.h"
+#include "android-base/utf8.h"
namespace android {
@@ -83,15 +84,16 @@
return {};
}
+ std::string overlay_path(loaded_idmap->OverlayApkPath());
+ auto fd = unique_fd(base::utf8::open(overlay_path.c_str(), O_RDONLY | O_CLOEXEC));
std::unique_ptr<AssetsProvider> overlay_assets;
- const std::string overlay_path(loaded_idmap->OverlayApkPath());
- if (IsFabricatedOverlay(overlay_path)) {
+ if (IsFabricatedOverlay(fd)) {
// Fabricated overlays do not contain resource definitions. All of the overlay resource values
// are defined inline in the idmap.
- overlay_assets = EmptyAssetsProvider::Create(overlay_path);
+ overlay_assets = EmptyAssetsProvider::Create(std::move(overlay_path));
} else {
// The overlay should be an APK.
- overlay_assets = ZipAssetsProvider::Create(overlay_path, flags);
+ overlay_assets = ZipAssetsProvider::Create(std::move(overlay_path), flags, std::move(fd));
}
if (overlay_assets == nullptr) {
return {};
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3fa369d..cc7e871 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -22,6 +22,7 @@
#include <iterator>
#include <map>
#include <set>
+#include <span>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -111,7 +112,7 @@
// A mapping from path of apk assets that could be target packages of overlays to the runtime
// package id of its first loaded package. Overlays currently can only override resources in the
// first package in the target resource table.
- std::unordered_map<std::string, uint8_t> target_assets_package_ids;
+ std::unordered_map<std::string_view, uint8_t> target_assets_package_ids;
// Overlay resources are not directly referenced by an application so their resource ids
// can change throughout the application's lifetime. Assign overlay package ids last.
@@ -134,7 +135,7 @@
if (auto loaded_idmap = apk_assets->GetLoadedIdmap(); loaded_idmap != nullptr) {
// The target package must precede the overlay package in the apk assets paths in order
// to take effect.
- auto iter = target_assets_package_ids.find(std::string(loaded_idmap->TargetApkPath()));
+ auto iter = target_assets_package_ids.find(loaded_idmap->TargetApkPath());
if (iter == target_assets_package_ids.end()) {
LOG(INFO) << "failed to find target package for overlay "
<< loaded_idmap->OverlayApkPath();
@@ -179,7 +180,7 @@
if (overlay_ref_table != nullptr) {
// If this package is from an overlay, use a dynamic reference table that can rewrite
// overlay resource ids to their corresponding target resource ids.
- new_group.dynamic_ref_table = overlay_ref_table;
+ new_group.dynamic_ref_table = std::move(overlay_ref_table);
}
DynamicRefTable* ref_table = new_group.dynamic_ref_table.get();
@@ -187,9 +188,9 @@
ref_table->mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
}
- // Add the package and to the set of packages with the same ID.
+ // Add the package to the set of packages with the same ID.
PackageGroup* package_group = &package_groups_[idx];
- package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
+ package_group->packages_.emplace_back().loaded_package_ = package.get();
package_group->cookies_.push_back(apk_assets_cookies[apk_assets]);
// Add the package name -> build time ID mappings.
@@ -201,29 +202,38 @@
if (auto apk_assets_path = apk_assets->GetPath()) {
// Overlay target ApkAssets must have been created using path based load apis.
- target_assets_package_ids.insert(std::make_pair(std::string(*apk_assets_path), package_id));
+ target_assets_package_ids.emplace(*apk_assets_path, package_id);
}
}
}
// Now assign the runtime IDs so that we have a build-time to runtime ID map.
- const auto package_groups_end = package_groups_.end();
- for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
- const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
- for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
- iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
- iter->dynamic_ref_table->mAssignedPackageId);
-
- // Add the alias resources to the dynamic reference table of every package group. Since
- // staging aliases can only be defined by the framework package (which is not a shared
- // library), the compile-time package id of the framework is the same across all packages
- // that compile against the framework.
- for (const auto& package : iter->packages_) {
- for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
- iter2->dynamic_ref_table->addAlias(entry.first, entry.second);
- }
- }
+ DynamicRefTable::AliasMap aliases;
+ for (const auto& group : package_groups_) {
+ const std::string& package_name = group.packages_[0].loaded_package_->GetPackageName();
+ const auto name_16 = String16(package_name.c_str(), package_name.size());
+ for (auto&& inner_group : package_groups_) {
+ inner_group.dynamic_ref_table->addMapping(name_16,
+ group.dynamic_ref_table->mAssignedPackageId);
}
+
+ for (const auto& package : group.packages_) {
+ const auto& package_aliases = package.loaded_package_->GetAliasResourceIdMap();
+ aliases.insert(aliases.end(), package_aliases.begin(), package_aliases.end());
+ }
+ }
+
+ if (!aliases.empty()) {
+ std::sort(aliases.begin(), aliases.end(), [](auto&& l, auto&& r) { return l.first < r.first; });
+
+ // Add the alias resources to the dynamic reference table of every package group. Since
+ // staging aliases can only be defined by the framework package (which is not a shared
+ // library), the compile-time package id of the framework is the same across all packages
+ // that compile against the framework.
+ for (auto& group : std::span(package_groups_.data(), package_groups_.size() - 1)) {
+ group.dynamic_ref_table->setAliases(aliases);
+ }
+ package_groups_.back().dynamic_ref_table->setAliases(std::move(aliases));
}
}
@@ -317,7 +327,7 @@
return &loaded_package->GetOverlayableMap();
}
-bool AssetManager2::GetOverlayablesToString(const android::StringPiece& package_name,
+bool AssetManager2::GetOverlayablesToString(android::StringPiece package_name,
std::string* out) const {
uint8_t package_id = 0U;
for (const auto& apk_assets : apk_assets_) {
@@ -364,7 +374,7 @@
const std::string name = ToFormattedResourceString(*res_name);
output.append(base::StringPrintf(
"resource='%s' overlayable='%s' actor='%s' policy='0x%08x'\n",
- name.c_str(), info->name.c_str(), info->actor.c_str(), info->policy_flags));
+ name.c_str(), info->name.data(), info->actor.data(), info->policy_flags));
}
}
}
@@ -492,7 +502,7 @@
continue;
}
- auto func = [&](const StringPiece& name, FileType type) {
+ auto func = [&](StringPiece name, FileType type) {
AssetDir::FileInfo info;
info.setFileName(String8(name.data(), name.size()));
info.setFileType(type);
@@ -1271,7 +1281,7 @@
return result;
}
-static bool Utf8ToUtf16(const StringPiece& str, std::u16string* out) {
+static bool Utf8ToUtf16(StringPiece str, std::u16string* out) {
ssize_t len =
utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size(), false);
if (len < 0) {
@@ -1346,22 +1356,22 @@
void AssetManager2::RebuildFilterList() {
for (PackageGroup& group : package_groups_) {
- for (ConfiguredPackage& impl : group.packages_) {
- // Destroy it.
- impl.filtered_configs_.~ByteBucketArray();
-
- // Re-create it.
- new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
-
+ for (ConfiguredPackage& package : group.packages_) {
+ package.filtered_configs_.forEachItem([](auto, auto& fcg) { fcg.type_entries.clear(); });
// Create the filters here.
- impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
- FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_id - 1);
+ package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
+ FilteredConfigGroup* group = nullptr;
for (const auto& type_entry : type_spec.type_entries) {
if (type_entry.config.match(configuration_)) {
- group.type_entries.push_back(&type_entry);
+ if (!group) {
+ group = &package.filtered_configs_.editItemAt(type_id - 1);
+ }
+ group->type_entries.push_back(&type_entry);
}
}
});
+ package.filtered_configs_.trimBuckets(
+ [](const auto& fcg) { return fcg.type_entries.empty(); });
}
}
}
@@ -1402,30 +1412,34 @@
std::unique_ptr<Theme> AssetManager2::NewTheme() {
constexpr size_t kInitialReserveSize = 32;
auto theme = std::unique_ptr<Theme>(new Theme(this));
+ theme->keys_.reserve(kInitialReserveSize);
theme->entries_.reserve(kInitialReserveSize);
return theme;
}
+void AssetManager2::ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags) const {
+ for (const PackageGroup& package_group : package_groups_) {
+ const auto loaded_package = package_group.packages_.front().loaded_package_;
+ if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
+ && !func(loaded_package->GetPackageName(),
+ package_group.dynamic_ref_table->mAssignedPackageId)) {
+ return;
+ }
+ }
+}
+
Theme::Theme(AssetManager2* asset_manager) : asset_manager_(asset_manager) {
}
Theme::~Theme() = default;
struct Theme::Entry {
- uint32_t attr_res_id;
ApkAssetsCookie cookie;
uint32_t type_spec_flags;
Res_value value;
};
-namespace {
-struct ThemeEntryKeyComparer {
- bool operator() (const Theme::Entry& entry, uint32_t attr_res_id) const noexcept {
- return entry.attr_res_id < attr_res_id;
- }
-};
-} // namespace
-
base::expected<std::monostate, NullOrIOError> Theme::ApplyStyle(uint32_t resid, bool force) {
ATRACE_NAME("Theme::ApplyStyle");
@@ -1454,18 +1468,20 @@
continue;
}
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), attr_res_id,
- ThemeEntryKeyComparer{});
- if (entry_it != entries_.end() && entry_it->attr_res_id == attr_res_id) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), attr_res_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
+ if (key_it != keys_.end() && *key_it == attr_res_id) {
if (is_undefined) {
// DATA_NULL_UNDEFINED clears the value of the attribute in the theme only when `force` is
- /// true.
+ // true.
+ keys_.erase(key_it);
entries_.erase(entry_it);
} else if (force) {
- *entry_it = Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value};
+ *entry_it = Entry{it->cookie, (*bag)->type_spec_flags, it->value};
}
} else {
- entries_.insert(entry_it, Entry{attr_res_id, it->cookie, (*bag)->type_spec_flags, it->value});
+ keys_.insert(key_it, attr_res_id);
+ entries_.insert(entry_it, Entry{it->cookie, (*bag)->type_spec_flags, it->value});
}
}
return {};
@@ -1476,6 +1492,7 @@
ATRACE_NAME("Theme::Rebase");
// Reset the entries without changing the vector capacity to prevent reallocations during
// ApplyStyle.
+ keys_.clear();
entries_.clear();
asset_manager_ = am;
for (size_t i = 0; i < style_count; i++) {
@@ -1484,16 +1501,14 @@
}
std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid) const {
-
constexpr const uint32_t kMaxIterations = 20;
uint32_t type_spec_flags = 0u;
for (uint32_t i = 0; i <= kMaxIterations; i++) {
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), resid,
- ThemeEntryKeyComparer{});
- if (entry_it == entries_.end() || entry_it->attr_res_id != resid) {
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), resid);
+ if (key_it == keys_.end() || *key_it != resid) {
return std::nullopt;
}
-
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
type_spec_flags |= entry_it->type_spec_flags;
if (entry_it->value.dataType == Res_value::TYPE_ATTRIBUTE) {
resid = entry_it->value.data;
@@ -1527,6 +1542,7 @@
}
void Theme::Clear() {
+ keys_.clear();
entries_.clear();
}
@@ -1538,11 +1554,12 @@
type_spec_flags_ = source.type_spec_flags_;
if (asset_manager_ == source.asset_manager_) {
+ keys_ = source.keys_;
entries_ = source.entries_;
} else {
- std::map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
- typedef std::map<int, int> SourceToDestinationRuntimePackageMap;
- std::map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
+ std::unordered_map<ApkAssetsCookie, ApkAssetsCookie> src_to_dest_asset_cookies;
+ using SourceToDestinationRuntimePackageMap = std::unordered_map<int, int>;
+ std::unordered_map<ApkAssetsCookie, SourceToDestinationRuntimePackageMap> src_asset_cookie_id_map;
// Determine which ApkAssets are loaded in both theme AssetManagers.
const auto src_assets = source.asset_manager_->GetApkAssets();
@@ -1570,15 +1587,17 @@
}
src_to_dest_asset_cookies.insert(std::make_pair(i, j));
- src_asset_cookie_id_map.insert(std::make_pair(i, package_map));
+ src_asset_cookie_id_map.insert(std::make_pair(i, std::move(package_map)));
break;
}
}
// Reset the data in the destination theme.
+ keys_.clear();
entries_.clear();
- for (const auto& entry : source.entries_) {
+ for (size_t i = 0, size = source.entries_.size(); i != size; ++i) {
+ const auto& entry = source.entries_[i];
bool is_reference = (entry.value.dataType == Res_value::TYPE_ATTRIBUTE
|| entry.value.dataType == Res_value::TYPE_REFERENCE
|| entry.value.dataType == Res_value::TYPE_DYNAMIC_ATTRIBUTE
@@ -1618,13 +1637,15 @@
}
}
+ const auto source_res_id = source.keys_[i];
+
// The package id of the attribute needs to be rewritten to the package id of the
// attribute in the destination.
- int attribute_dest_package_id = get_package_id(entry.attr_res_id);
+ int attribute_dest_package_id = get_package_id(source_res_id);
if (attribute_dest_package_id != 0x01) {
// Find the cookie of the attribute resource id in the source AssetManager
base::expected<FindEntryResult, NullOrIOError> attribute_entry_result =
- source.asset_manager_->FindEntry(entry.attr_res_id, 0 /* density_override */ ,
+ source.asset_manager_->FindEntry(source_res_id, 0 /* density_override */ ,
true /* stop_at_first_match */,
true /* ignore_configuration */);
if (UNLIKELY(IsIOError(attribute_entry_result))) {
@@ -1648,16 +1669,15 @@
attribute_dest_package_id = attribute_dest_package->second;
}
- auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(entry.attr_res_id),
- get_entry_id(entry.attr_res_id));
- Theme::Entry new_entry{dest_attr_id, data_dest_cookie, entry.type_spec_flags,
- Res_value{.dataType = entry.value.dataType,
- .data = attribute_data}};
-
+ auto dest_attr_id = make_resid(attribute_dest_package_id, get_type_id(source_res_id),
+ get_entry_id(source_res_id));
+ const auto key_it = std::lower_bound(keys_.begin(), keys_.end(), dest_attr_id);
+ const auto entry_it = entries_.begin() + (key_it - keys_.begin());
// Since the entries were cleared, the attribute resource id has yet been mapped to any value.
- auto entry_it = std::lower_bound(entries_.begin(), entries_.end(), dest_attr_id,
- ThemeEntryKeyComparer{});
- entries_.insert(entry_it, new_entry);
+ keys_.insert(key_it, dest_attr_id);
+ entries_.insert(entry_it, Entry{data_dest_cookie, entry.type_spec_flags,
+ Res_value{.dataType = entry.value.dataType,
+ .data = attribute_data}});
}
}
return {};
@@ -1665,9 +1685,11 @@
void Theme::Dump() const {
LOG(INFO) << base::StringPrintf("Theme(this=%p, AssetManager2=%p)", this, asset_manager_);
- for (auto& entry : entries_) {
+ for (size_t i = 0, size = keys_.size(); i != size; ++i) {
+ auto res_id = keys_[i];
+ const auto& entry = entries_[i];
LOG(INFO) << base::StringPrintf(" entry(0x%08x)=(0x%08x) type=(0x%02x), cookie(%d)",
- entry.attr_res_id, entry.value.data, entry.value.dataType,
+ res_id, entry.value.data, entry.value.dataType,
entry.cookie);
}
}
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index bce34d3..b9264c5 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -92,21 +92,27 @@
last_mod_time_(last_mod_time) {}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
- package_property_t flags) {
+ package_property_t flags,
+ base::unique_fd fd) {
+ const auto released_fd = fd.ok() ? fd.release() : -1;
ZipArchiveHandle handle;
- if (int32_t result = OpenArchive(path.c_str(), &handle); result != 0) {
+ if (int32_t result = released_fd < 0 ? OpenArchive(path.c_str(), &handle)
+ : OpenArchiveFd(released_fd, path.c_str(), &handle)) {
LOG(ERROR) << "Failed to open APK '" << path << "': " << ::ErrorCodeString(result);
CloseArchive(handle);
return {};
}
struct stat sb{.st_mtime = -1};
- if (stat(path.c_str(), &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to stat file '" << path << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(path.c_str())) {
+ if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to stat file '" << path << "': "
+ << base::SystemErrorCodeToString(errno);
+ }
}
return std::unique_ptr<ZipAssetsProvider>(
@@ -133,12 +139,15 @@
}
struct stat sb{.st_mtime = -1};
- if (fstat(released_fd, &sb) < 0) {
- // Stat requires execute permissions on all directories path to the file. If the process does
- // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
- // always have to return true.
- LOG(WARNING) << "Failed to fstat file '" << friendly_name << "': "
- << base::SystemErrorCodeToString(errno);
+ // Skip all up-to-date checks if the file won't ever change.
+ if (!isReadonlyFilesystem(released_fd)) {
+ if (fstat(released_fd, &sb) < 0) {
+ // Stat requires execute permissions on all directories path to the file. If the process does
+ // not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
+ // always have to return true.
+ LOG(WARNING) << "Failed to fstat file '" << friendly_name
+ << "': " << base::SystemErrorCodeToString(errno);
+ }
}
return std::unique_ptr<ZipAssetsProvider>(
@@ -211,8 +220,7 @@
}
bool ZipAssetsProvider::ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f)
- const {
+ const std::function<void(StringPiece, FileType)>& f) const {
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
root_path_full += '/';
@@ -238,8 +246,7 @@
if (!leaf_file_path.empty()) {
auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
if (iter != leaf_file_path.end()) {
- std::string dir =
- leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+ std::string dir(leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)));
dirs.insert(std::move(dir));
} else {
f(leaf_file_path, kFileTypeRegular);
@@ -277,6 +284,9 @@
}
bool ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == -1) {
+ return true;
+ }
struct stat sb{};
if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
// If fstat fails on the zip archive, return true so the zip archive the resource system does
@@ -290,7 +300,7 @@
: dir_(std::forward<std::string>(path)), last_mod_time_(last_mod_time) {}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
- struct stat sb{};
+ struct stat sb;
const int result = stat(path.c_str(), &sb);
if (result == -1) {
LOG(ERROR) << "Failed to find directory '" << path << "'.";
@@ -306,8 +316,9 @@
path += OS_PATH_SEPARATOR;
}
- return std::unique_ptr<DirectoryAssetsProvider>(new DirectoryAssetsProvider(std::move(path),
- sb.st_mtime));
+ const bool isReadonly = isReadonlyFilesystem(path.c_str());
+ return std::unique_ptr<DirectoryAssetsProvider>(
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -324,8 +335,7 @@
bool DirectoryAssetsProvider::ForEachFile(
const std::string& /* root_path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */)
- const {
+ const std::function<void(StringPiece, FileType)>& /* f */) const {
return true;
}
@@ -338,7 +348,10 @@
}
bool DirectoryAssetsProvider::IsUpToDate() const {
- struct stat sb{};
+ if (last_mod_time_ == -1) {
+ return true;
+ }
+ struct stat sb;
if (stat(dir_.c_str(), &sb) < 0) {
// If stat fails on the zip archive, return true so the zip archive the resource system does
// attempt to refresh the ApkAsset.
@@ -373,8 +386,7 @@
}
bool MultiAssetsProvider::ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f)
- const {
+ const std::function<void(StringPiece, FileType)>& f) const {
return primary_->ForEachFile(root_path, f) && secondary_->ForEachFile(root_path, f);
}
@@ -397,8 +409,8 @@
return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider({}));
}
-std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(const std::string& path) {
- return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(path));
+std::unique_ptr<AssetsProvider> EmptyAssetsProvider::Create(std::string path) {
+ return std::unique_ptr<EmptyAssetsProvider>(new EmptyAssetsProvider(std::move(path)));
}
std::unique_ptr<Asset> EmptyAssetsProvider::OpenInternal(const std::string& /* path */,
@@ -412,7 +424,7 @@
bool EmptyAssetsProvider::ForEachFile(
const std::string& /* root_path */,
- const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+ const std::function<void(StringPiece, FileType)>& /* f */) const {
return true;
}
@@ -435,4 +447,4 @@
return true;
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 19ead95..93a7d17 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -637,7 +637,7 @@
return true;
}
-bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+bool ConfigDescription::Parse(StringPiece str, ConfigDescription* out) {
std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
ConfigDescription config;
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index e122d48..f3d2443 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -274,8 +274,7 @@
target_apk_path_(target_apk_path),
idmap_last_mod_time_(getFileModDate(idmap_path_.data())) {}
-std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data) {
+std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
ATRACE_CALL();
size_t data_size = idmap_data.size();
auto data_ptr = reinterpret_cast<const uint8_t*>(idmap_data.data());
@@ -365,7 +364,7 @@
// Can't use make_unique because LoadedIdmap constructor is private.
return std::unique_ptr<LoadedIdmap>(
- new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
+ new LoadedIdmap(std::string(idmap_path), header, data_header, target_entries,
target_inline_entries, target_inline_entry_values, configurations,
overlay_entries, std::move(idmap_string_pool), *target_path, *overlay_path));
}
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 386f718..c0fdfe2 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -645,16 +645,16 @@
}
std::string name;
- util::ReadUtf16StringFromDevice(overlayable->name, arraysize(overlayable->name), &name);
+ util::ReadUtf16StringFromDevice(overlayable->name, std::size(overlayable->name), &name);
std::string actor;
- util::ReadUtf16StringFromDevice(overlayable->actor, arraysize(overlayable->actor), &actor);
-
- if (loaded_package->overlayable_map_.find(name) !=
- loaded_package->overlayable_map_.end()) {
- LOG(ERROR) << "Multiple <overlayable> blocks with the same name '" << name << "'.";
+ util::ReadUtf16StringFromDevice(overlayable->actor, std::size(overlayable->actor), &actor);
+ auto [name_to_actor_it, inserted] =
+ loaded_package->overlayable_map_.emplace(std::move(name), std::move(actor));
+ if (!inserted) {
+ LOG(ERROR) << "Multiple <overlayable> blocks with the same name '"
+ << name_to_actor_it->first << "'.";
return {};
}
- loaded_package->overlayable_map_.emplace(name, actor);
// Iterate over the overlayable policy chunks contained within the overlayable chunk data
ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size());
@@ -669,7 +669,6 @@
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small.";
return {};
}
-
if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref))
< dtohl(policy_header->entry_count)) {
LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries.";
@@ -691,8 +690,8 @@
// Add the pairing of overlayable properties and resource ids to the package
OverlayableInfo overlayable_info {
- .name = name,
- .actor = actor,
+ .name = name_to_actor_it->first,
+ .actor = name_to_actor_it->second,
.policy_flags = policy_header->policy_flags
};
loaded_package->overlayable_infos_.emplace_back(std::move(overlayable_info), std::move(ids));
@@ -736,6 +735,7 @@
const auto entry_end = entry_begin + dtohl(lib_alias->count);
std::unordered_set<uint32_t> finalized_ids;
finalized_ids.reserve(entry_end - entry_begin);
+ loaded_package->alias_id_map_.reserve(entry_end - entry_begin);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
if (!entry_iter) {
LOG(ERROR) << "NULL ResTable_staged_alias_entry record??";
@@ -749,13 +749,20 @@
}
auto staged_id = dtohl(entry_iter->stagedResId);
- auto [_, success] = loaded_package->alias_id_map_.emplace(staged_id, finalized_id);
- if (!success) {
+ loaded_package->alias_id_map_.emplace_back(staged_id, finalized_id);
+ }
+
+ std::sort(loaded_package->alias_id_map_.begin(), loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first < r.first; });
+ const auto duplicate_it =
+ std::adjacent_find(loaded_package->alias_id_map_.begin(),
+ loaded_package->alias_id_map_.end(),
+ [](auto&& l, auto&& r) { return l.first == r.first; });
+ if (duplicate_it != loaded_package->alias_id_map_.end()) {
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
- staged_id);
+ duplicate_it->first);
return {};
}
- }
} break;
default:
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
index d87a3ce..272a988 100644
--- a/libs/androidfw/Locale.cpp
+++ b/libs/androidfw/Locale.cpp
@@ -66,7 +66,7 @@
return std::all_of(std::begin(str), std::end(str), ::isdigit);
}
-bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+bool LocaleValue::InitFromFilterString(StringPiece str) {
// A locale (as specified in the filter) is an underscore separated name such
// as "en_US", "en_Latn_US", or "en_US_POSIX".
std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
@@ -132,11 +132,11 @@
return true;
}
-bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+bool LocaleValue::InitFromBcp47Tag(StringPiece bcp47tag) {
return InitFromBcp47TagImpl(bcp47tag, '-');
}
-bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+bool LocaleValue::InitFromBcp47TagImpl(StringPiece bcp47tag, const char separator) {
std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
if (subtags.size() == 1) {
set_language(subtags[0].c_str());
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 267190a..31516dc 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -33,7 +33,9 @@
#include <type_traits>
#include <vector>
+#include <android-base/file.h>
#include <android-base/macros.h>
+#include <android-base/utf8.h>
#include <androidfw/ByteBucketArray.h>
#include <androidfw/ResourceTypes.h>
#include <androidfw/TypeWrappers.h>
@@ -236,12 +238,23 @@
}
bool IsFabricatedOverlay(const std::string& path) {
- std::ifstream fin(path);
- uint32_t magic;
- if (fin.read(reinterpret_cast<char*>(&magic), sizeof(uint32_t))) {
- return magic == kFabricatedOverlayMagic;
+ return IsFabricatedOverlay(path.c_str());
+}
+
+bool IsFabricatedOverlay(const char* path) {
+ auto fd = base::unique_fd(base::utf8::open(path, O_RDONLY|O_CLOEXEC));
+ if (fd < 0) {
+ return false;
}
- return false;
+ return IsFabricatedOverlay(fd);
+}
+
+bool IsFabricatedOverlay(base::borrowed_fd fd) {
+ uint32_t magic;
+ if (!base::ReadFullyAtOffset(fd, &magic, sizeof(magic), 0)) {
+ return false;
+ }
+ return magic == kFabricatedOverlayMagic;
}
static bool assertIdmapHeader(const void* idmap, size_t size) {
@@ -6988,11 +7001,10 @@
DynamicRefTable::DynamicRefTable() : DynamicRefTable(0, false) {}
DynamicRefTable::DynamicRefTable(uint8_t packageId, bool appAsLib)
- : mAssignedPackageId(packageId)
+ : mLookupTable()
+ , mAssignedPackageId(packageId)
, mAppAsLib(appAsLib)
{
- memset(mLookupTable, 0, sizeof(mLookupTable));
-
// Reserved package ids
mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID;
mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID;
@@ -7073,10 +7085,6 @@
mLookupTable[buildPackageId] = runtimePackageId;
}
-void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
- mAliasId[stagedId] = finalizedId;
-}
-
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
@@ -7086,11 +7094,12 @@
return NO_ERROR;
}
- auto alias_id = mAliasId.find(res);
- if (alias_id != mAliasId.end()) {
+ const auto alias_it = std::lower_bound(mAliasId.begin(), mAliasId.end(), res,
+ [](const AliasMap::value_type& pair, uint32_t val) { return pair.first < val; });
+ if (alias_it != mAliasId.end() && alias_it->first == res) {
// Rewrite the resource id to its alias resource id. Since the alias resource id is a
// compile-time id, it still needs to be resolved further.
- res = alias_id->second;
+ res = alias_it->second;
}
if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
diff --git a/libs/androidfw/ResourceUtils.cpp b/libs/androidfw/ResourceUtils.cpp
index 87fb2c0..ccb6156 100644
--- a/libs/androidfw/ResourceUtils.cpp
+++ b/libs/androidfw/ResourceUtils.cpp
@@ -18,7 +18,7 @@
namespace android {
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry) {
*out_package = "";
*out_type = "";
@@ -33,16 +33,16 @@
while (current != end) {
if (out_type->size() == 0 && *current == '/') {
has_type_separator = true;
- out_type->assign(start, current - start);
+ *out_type = StringPiece(start, current - start);
start = current + 1;
} else if (out_package->size() == 0 && *current == ':') {
has_package_separator = true;
- out_package->assign(start, current - start);
+ *out_package = StringPiece(start, current - start);
start = current + 1;
}
current++;
}
- out_entry->assign(start, end - start);
+ *out_entry = StringPiece(start, end - start);
return !(has_package_separator && out_package->empty()) &&
!(has_type_separator && out_type->empty());
@@ -50,7 +50,7 @@
base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
- const StringPiece& package_name) {
+ StringPiece package_name) {
AssetManager2::ResourceName name{
.package = package_name.data(),
.package_len = package_name.size(),
diff --git a/libs/androidfw/StringPool.cpp b/libs/androidfw/StringPool.cpp
index b59e906..1cb8df3 100644
--- a/libs/androidfw/StringPool.cpp
+++ b/libs/androidfw/StringPool.cpp
@@ -161,16 +161,15 @@
return entry_->context;
}
-StringPool::Ref StringPool::MakeRef(const StringPiece& str) {
+StringPool::Ref StringPool::MakeRef(StringPiece str) {
return MakeRefImpl(str, Context{}, true);
}
-StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) {
+StringPool::Ref StringPool::MakeRef(StringPiece str, const Context& context) {
return MakeRefImpl(str, context, true);
}
-StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context,
- bool unique) {
+StringPool::Ref StringPool::MakeRefImpl(StringPiece str, const Context& context, bool unique) {
if (unique) {
auto range = indexed_strings_.equal_range(str);
for (auto iter = range.first; iter != range.second; ++iter) {
@@ -181,7 +180,7 @@
}
std::unique_ptr<Entry> entry(new Entry());
- entry->value = str.to_string();
+ entry->value = std::string(str);
entry->context = context;
entry->index_ = strings_.size();
entry->ref_ = 0;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 52ad0dc..be55fe8 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -42,7 +42,7 @@
}
}
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
ssize_t utf16_length =
utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
if (utf16_length <= 0) {
@@ -56,7 +56,7 @@
return utf16;
}
-std::string Utf16ToUtf8(const StringPiece16& utf16) {
+std::string Utf16ToUtf8(StringPiece16 utf16) {
ssize_t utf8_length = utf16_to_utf8_length(utf16.data(), utf16.length());
if (utf8_length <= 0) {
return {};
@@ -68,7 +68,7 @@
return utf8;
}
-std::string Utf8ToModifiedUtf8(const std::string& utf8) {
+std::string Utf8ToModifiedUtf8(std::string_view utf8) {
// Java uses Modified UTF-8 which only supports the 1, 2, and 3 byte formats of UTF-8. To encode
// 4 byte UTF-8 codepoints, Modified UTF-8 allows the use of surrogate pairs in the same format
// of CESU-8 surrogate pairs. Calculate the size of the utf8 string with all 4 byte UTF-8
@@ -86,7 +86,7 @@
// Early out if no 4 byte codepoints are found
if (size == modified_size) {
- return utf8;
+ return std::string(utf8);
}
std::string output;
@@ -115,7 +115,7 @@
return output;
}
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8) {
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8) {
// The UTF-8 representation will have a byte length less than or equal to the Modified UTF-8
// representation.
std::string output;
@@ -170,30 +170,28 @@
return output;
}
-static std::vector<std::string> SplitAndTransform(
- const StringPiece& str, char sep, const std::function<char(char)>& f) {
+template <class Func>
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, Func&& f) {
std::vector<std::string> parts;
const StringPiece::const_iterator end = std::end(str);
StringPiece::const_iterator start = std::begin(str);
StringPiece::const_iterator current;
do {
current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).to_string());
- if (f) {
- std::string& part = parts.back();
- std::transform(part.begin(), part.end(), part.begin(), f);
- }
+ parts.emplace_back(StringPiece(start, current - start));
+ std::string& part = parts.back();
+ std::transform(part.begin(), part.end(), part.begin(), f);
start = current + 1;
} while (current != end);
return parts;
}
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
- return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+ return SplitAndTransform(str, sep, [](char c) { return ::tolower(c); });
}
std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer) {
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
+ auto data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
uint8_t* p = data.get();
for (const auto& block : buffer) {
memcpy(p, block.buffer.get(), block.size);
@@ -211,7 +209,7 @@
std::string GetString(const android::ResStringPool& pool, size_t idx) {
if (auto str = pool.string8At(idx); str.ok()) {
- return ModifiedUtf8ToUtf8(str->to_string());
+ return ModifiedUtf8ToUtf8(*str);
}
return Utf16ToUtf8(GetString16(pool, idx));
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 1bde792..e4d1218 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -17,6 +17,7 @@
#ifndef ANDROIDFW_ASSETMANAGER2_H_
#define ANDROIDFW_ASSETMANAGER2_H_
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
#include <array>
@@ -124,8 +125,7 @@
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
// Returns a string representation of the overlayable API of a package.
- bool GetOverlayablesToString(const android::StringPiece& package_name,
- std::string* out) const;
+ bool GetOverlayablesToString(android::StringPiece package_name, std::string* out) const;
const std::unordered_map<std::string, std::string>* GetOverlayableMapForPackage(
uint32_t package_id) const;
@@ -321,17 +321,8 @@
// Creates a new Theme from this AssetManager.
std::unique_ptr<Theme> NewTheme();
- void ForEachPackage(const std::function<bool(const std::string&, uint8_t)> func,
- package_property_t excluded_property_flags = 0U) const {
- for (const PackageGroup& package_group : package_groups_) {
- const auto loaded_package = package_group.packages_.front().loaded_package_;
- if ((loaded_package->GetPropertyFlags() & excluded_property_flags) == 0U
- && !func(loaded_package->GetPackageName(),
- package_group.dynamic_ref_table->mAssignedPackageId)) {
- return;
- }
- }
- }
+ void ForEachPackage(base::function_ref<bool(const std::string&, uint8_t)> func,
+ package_property_t excluded_property_flags = 0U) const;
void DumpToLog() const;
@@ -572,6 +563,7 @@
AssetManager2* asset_manager_ = nullptr;
uint32_t type_spec_flags_ = 0u;
+ std::vector<uint32_t> keys_;
std::vector<Entry> entries_;
};
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index 966ec74..7891194 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -46,7 +46,7 @@
// Iterate over all files and directories provided by the interface. The order of iteration is
// stable.
virtual bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const = 0;
+ const std::function<void(StringPiece, FileType)>& f) const = 0;
// Retrieves the path to the contents of the AssetsProvider on disk. The path could represent an
// APk, a directory, or some other file type.
@@ -80,8 +80,8 @@
// Supplies assets from a zip archive.
struct ZipAssetsProvider : public AssetsProvider {
- static std::unique_ptr<ZipAssetsProvider> Create(std::string path,
- package_property_t flags);
+ static std::unique_ptr<ZipAssetsProvider> Create(std::string path, package_property_t flags,
+ base::unique_fd fd = {});
static std::unique_ptr<ZipAssetsProvider> Create(base::unique_fd fd,
std::string friendly_name,
@@ -90,7 +90,7 @@
off64_t len = kUnknownLength);
bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ const std::function<void(StringPiece, FileType)>& f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -132,7 +132,7 @@
static std::unique_ptr<DirectoryAssetsProvider> Create(std::string root_dir);
bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ const std::function<void(StringPiece, FileType)>& f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -157,7 +157,7 @@
std::unique_ptr<AssetsProvider>&& secondary);
bool ForEachFile(const std::string& root_path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ const std::function<void(StringPiece, FileType)>& f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
@@ -181,10 +181,10 @@
// Does not provide any assets.
struct EmptyAssetsProvider : public AssetsProvider {
static std::unique_ptr<AssetsProvider> Create();
- static std::unique_ptr<AssetsProvider> Create(const std::string& path);
+ static std::unique_ptr<AssetsProvider> Create(std::string path);
bool ForEachFile(const std::string& path,
- const std::function<void(const StringPiece&, FileType)>& f) const override;
+ const std::function<void(StringPiece, FileType)>& f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
diff --git a/libs/androidfw/include/androidfw/ByteBucketArray.h b/libs/androidfw/include/androidfw/ByteBucketArray.h
index 949c9445..ca0a9ed 100644
--- a/libs/androidfw/include/androidfw/ByteBucketArray.h
+++ b/libs/androidfw/include/androidfw/ByteBucketArray.h
@@ -17,6 +17,7 @@
#ifndef __BYTE_BUCKET_ARRAY_H
#define __BYTE_BUCKET_ARRAY_H
+#include <algorithm>
#include <cstdint>
#include <cstring>
@@ -31,14 +32,16 @@
template <typename T>
class ByteBucketArray {
public:
- ByteBucketArray() : default_() { memset(buckets_, 0, sizeof(buckets_)); }
+ ByteBucketArray() {
+ memset(buckets_, 0, sizeof(buckets_));
+ }
~ByteBucketArray() {
- for (size_t i = 0; i < kNumBuckets; i++) {
- if (buckets_[i] != NULL) {
- delete[] buckets_[i];
- }
- }
+ deleteBuckets();
+ }
+
+ void clear() {
+ deleteBuckets();
memset(buckets_, 0, sizeof(buckets_));
}
@@ -53,7 +56,7 @@
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
+ if (bucket == nullptr) {
return default_;
}
return bucket[0x0f & static_cast<uint8_t>(index)];
@@ -64,9 +67,9 @@
<< ") with size=" << size();
uint8_t bucket_index = static_cast<uint8_t>(index) >> 4;
- T* bucket = buckets_[bucket_index];
- if (bucket == NULL) {
- bucket = buckets_[bucket_index] = new T[kBucketSize]();
+ T*& bucket = buckets_[bucket_index];
+ if (bucket == nullptr) {
+ bucket = new T[kBucketSize]();
}
return bucket[0x0f & static_cast<uint8_t>(index)];
}
@@ -80,11 +83,44 @@
return true;
}
+ template <class Func>
+ void forEachItem(Func f) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ for (size_t j = 0; j < kBucketSize; j++) {
+ f((i << 4) | j, bucket[j]);
+ }
+ }
+ }
+ }
+
+ template <class Func>
+ void trimBuckets(Func isEmptyFunc) {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ const auto bucket = buckets_[i];
+ if (bucket != nullptr) {
+ if (std::all_of(bucket, bucket + kBucketSize, isEmptyFunc)) {
+ delete[] bucket;
+ buckets_[i] = nullptr;
+ }
+ }
+ }
+ }
+
private:
enum { kNumBuckets = 16, kBucketSize = 16 };
+ void deleteBuckets() {
+ for (size_t i = 0; i < kNumBuckets; i++) {
+ if (buckets_[i] != nullptr) {
+ delete[] buckets_[i];
+ }
+ }
+ }
+
T* buckets_[kNumBuckets];
- T default_;
+ static inline const T default_ = {};
};
} // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 61d10cd..71087cd 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -72,7 +72,7 @@
* The resulting configuration has the appropriate sdkVersion defined
* for backwards compatibility.
*/
- static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+ static bool Parse(android::StringPiece str, ConfigDescription* out = nullptr);
/**
* If the configuration uses an axis that was added after
diff --git a/libs/androidfw/include/androidfw/IDiagnostics.h b/libs/androidfw/include/androidfw/IDiagnostics.h
index 273b05a..4d5844e 100644
--- a/libs/androidfw/include/androidfw/IDiagnostics.h
+++ b/libs/androidfw/include/androidfw/IDiagnostics.h
@@ -35,7 +35,7 @@
public:
DiagMessage() = default;
- explicit DiagMessage(const android::StringPiece& src) : source_(src) {
+ explicit DiagMessage(android::StringPiece src) : source_(src) {
}
explicit DiagMessage(const Source& src) : source_(src) {
@@ -61,7 +61,7 @@
template <>
inline DiagMessage& DiagMessage::operator<<(const ::std::u16string& value) {
- message_ << android::StringPiece16(value);
+ message_ << value;
return *this;
}
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index a1cbbbf..f173e6e 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -157,8 +157,7 @@
class LoadedIdmap {
public:
// Loads an IDMAP from a chunk of memory. Returns nullptr if the IDMAP data was malformed.
- static std::unique_ptr<LoadedIdmap> Load(const StringPiece& idmap_path,
- const StringPiece& idmap_data);
+ static std::unique_ptr<LoadedIdmap> Load(StringPiece idmap_path, StringPiece idmap_data);
// Returns the path to the IDMAP.
std::string_view IdmapPath() const {
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 79d96282..4d12885 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,8 +99,8 @@
};
struct OverlayableInfo {
- std::string name;
- std::string actor;
+ std::string_view name;
+ std::string_view actor;
uint32_t policy_flags;
};
@@ -275,7 +275,7 @@
return overlayable_map_;
}
- const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
+ const std::vector<std::pair<uint32_t, uint32_t>>& GetAliasResourceIdMap() const {
return alias_id_map_;
}
@@ -295,8 +295,8 @@
std::unordered_map<uint8_t, TypeSpec> type_specs_;
ByteBucketArray<uint32_t> resource_ids_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
- std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
- std::map<uint32_t, uint32_t> alias_id_map_;
+ std::vector<std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
+ std::vector<std::pair<uint32_t, uint32_t>> alias_id_map_;
// A map of overlayable name to actor
std::unordered_map<std::string, std::string> overlayable_map_;
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
index 484ed79..8934bed 100644
--- a/libs/androidfw/include/androidfw/Locale.h
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -39,10 +39,10 @@
/**
* Initialize this LocaleValue from a config string.
*/
- bool InitFromFilterString(const android::StringPiece& config);
+ bool InitFromFilterString(android::StringPiece config);
// Initializes this LocaleValue from a BCP-47 locale tag.
- bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+ bool InitFromBcp47Tag(android::StringPiece bcp47tag);
/**
* Initialize this LocaleValue from parts of a vector.
@@ -70,7 +70,7 @@
inline bool operator>(const LocaleValue& o) const;
private:
- bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+ bool InitFromBcp47TagImpl(android::StringPiece bcp47tag, const char separator);
void set_language(const char* language);
void set_region(const char* language);
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index b2b95b7..52321da 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -21,6 +21,7 @@
#define _LIBS_UTILS_RESOURCE_TYPES_H
#include <android-base/expected.h>
+#include <android-base/unique_fd.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
@@ -58,6 +59,8 @@
// Returns whether or not the path represents a fabricated overlay.
bool IsFabricatedOverlay(const std::string& path);
+bool IsFabricatedOverlay(const char* path);
+bool IsFabricatedOverlay(android::base::borrowed_fd fd);
/**
* In C++11, char16_t is defined as *at least* 16 bits. We do a lot of
@@ -1882,7 +1885,10 @@
void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
- void addAlias(uint32_t stagedId, uint32_t finalizedId);
+ using AliasMap = std::vector<std::pair<uint32_t, uint32_t>>;
+ void setAliases(AliasMap aliases) {
+ mAliasId = std::move(aliases);
+ }
// Returns whether or not the value must be looked up.
bool requiresLookup(const Res_value* value) const;
@@ -1896,12 +1902,12 @@
return mEntries;
}
-private:
- uint8_t mAssignedPackageId;
- uint8_t mLookupTable[256];
- KeyedVector<String16, uint8_t> mEntries;
- bool mAppAsLib;
- std::map<uint32_t, uint32_t> mAliasId;
+ private:
+ uint8_t mLookupTable[256];
+ uint8_t mAssignedPackageId;
+ bool mAppAsLib;
+ KeyedVector<String16, uint8_t> mEntries;
+ AliasMap mAliasId;
};
bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index bd1c440..2d90a52 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -25,14 +25,14 @@
// Extracts the package, type, and name from a string of the format: [[package:]type/]name
// Validation must be performed on each extracted piece.
// Returns false if there was a syntax error.
-bool ExtractResourceName(const StringPiece& str, StringPiece* out_package, StringPiece* out_type,
+bool ExtractResourceName(StringPiece str, StringPiece* out_package, StringPiece* out_type,
StringPiece* out_entry);
// Convert a type_string_ref, entry_string_ref, and package to AssetManager2::ResourceName.
// Useful for getting resource name without re-running AssetManager2::FindEntry searches.
base::expected<AssetManager2::ResourceName, NullOrIOError> ToResourceName(
const StringPoolRef& type_string_ref, const StringPoolRef& entry_string_ref,
- const StringPiece& package_name);
+ StringPiece package_name);
// Formats a ResourceName to "package:type/entry_name".
std::string ToFormattedResourceString(const AssetManager2::ResourceName& resource_name);
diff --git a/libs/androidfw/include/androidfw/Source.h b/libs/androidfw/include/androidfw/Source.h
index 0421a91..ddc9ba4 100644
--- a/libs/androidfw/include/androidfw/Source.h
+++ b/libs/androidfw/include/androidfw/Source.h
@@ -34,15 +34,14 @@
Source() = default;
- inline Source(const android::StringPiece& path) : path(path.to_string()) { // NOLINT(implicit)
+ inline Source(android::StringPiece path) : path(path) { // NOLINT(implicit)
}
- inline Source(const android::StringPiece& path, const android::StringPiece& archive)
- : path(path.to_string()), archive(archive.to_string()) {
+ inline Source(android::StringPiece path, android::StringPiece archive)
+ : path(path), archive(archive) {
}
- inline Source(const android::StringPiece& path, size_t line)
- : path(path.to_string()), line(line) {
+ inline Source(android::StringPiece path, size_t line) : path(path), line(line) {
}
inline Source WithLine(size_t line) const {
diff --git a/libs/androidfw/include/androidfw/StringPiece.h b/libs/androidfw/include/androidfw/StringPiece.h
index fac2fa4..f6cc64e 100644
--- a/libs/androidfw/include/androidfw/StringPiece.h
+++ b/libs/androidfw/include/androidfw/StringPiece.h
@@ -19,307 +19,37 @@
#include <ostream>
#include <string>
+#include <string_view>
-#include "utils/JenkinsHash.h"
#include "utils/Unicode.h"
namespace android {
-// Read only wrapper around basic C strings. Prevents excessive copying.
-// StringPiece does not own the data it is wrapping. The lifetime of the underlying
-// data must outlive this StringPiece.
-//
-// WARNING: When creating from std::basic_string<>, moving the original
-// std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
-// BasicStringPiece<> should only be used transitively.
-//
-// NOTE: When creating an std::pair<StringPiece, T> using std::make_pair(),
-// passing an std::string will first copy the string, then create a StringPiece
-// on the copy, which is then immediately destroyed.
-// Instead, create a StringPiece explicitly:
-//
-// std::string my_string = "foo";
-// std::make_pair<StringPiece, T>(StringPiece(my_string), ...);
-template <typename TChar>
-class BasicStringPiece {
- public:
- using const_iterator = const TChar*;
- using difference_type = size_t;
- using size_type = size_t;
-
- // End of string marker.
- constexpr static const size_t npos = static_cast<size_t>(-1);
-
- BasicStringPiece();
- BasicStringPiece(const BasicStringPiece<TChar>& str);
- BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(google-explicit-constructor)
- BasicStringPiece(const TChar* str); // NOLINT(google-explicit-constructor)
- BasicStringPiece(const TChar* str, size_t len);
-
- BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
- BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
-
- BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const;
- BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const;
-
- const TChar* data() const;
- size_t length() const;
- size_t size() const;
- bool empty() const;
- std::basic_string<TChar> to_string() const;
-
- bool contains(const BasicStringPiece<TChar>& rhs) const;
- int compare(const BasicStringPiece<TChar>& rhs) const;
- bool operator<(const BasicStringPiece<TChar>& rhs) const;
- bool operator>(const BasicStringPiece<TChar>& rhs) const;
- bool operator==(const BasicStringPiece<TChar>& rhs) const;
- bool operator!=(const BasicStringPiece<TChar>& rhs) const;
-
- const_iterator begin() const;
- const_iterator end() const;
-
- private:
- const TChar* data_;
- size_t length_;
-};
+template <class T>
+using BasicStringPiece = std::basic_string_view<T>;
using StringPiece = BasicStringPiece<char>;
using StringPiece16 = BasicStringPiece<char16_t>;
-//
-// BasicStringPiece implementation.
-//
-
-template <typename TChar>
-constexpr const size_t BasicStringPiece<TChar>::npos;
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece() : data_(nullptr), length_(0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str)
- : data_(str.data_), length_(str.length_) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str)
- : data_(str.data()), length_(str.length()) {}
-
-template <>
-inline BasicStringPiece<char>::BasicStringPiece(const char* str)
- : data_(str), length_(str != nullptr ? strlen(str) : 0) {}
-
-template <>
-inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str)
- : data_(str), length_(str != nullptr ? strlen16(str) : 0) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len)
- : data_(str), length_(len) {}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
- const BasicStringPiece<TChar>& rhs) {
- data_ = rhs.data_;
- length_ = rhs.length_;
- return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
- data_ = str;
- length_ = len;
- return *this;
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
- if (len == npos) {
- len = length_ - start;
- }
-
- if (start > length_ || start + len > length_) {
- return BasicStringPiece<TChar>();
- }
- return BasicStringPiece<TChar>(data_ + start, len);
-}
-
-template <typename TChar>
-inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
- BasicStringPiece<TChar>::const_iterator begin,
- BasicStringPiece<TChar>::const_iterator end) const {
- return BasicStringPiece<TChar>(begin, end - begin);
-}
-
-template <typename TChar>
-inline const TChar* BasicStringPiece<TChar>::data() const {
- return data_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::length() const {
- return length_;
-}
-
-template <typename TChar>
-inline size_t BasicStringPiece<TChar>::size() const {
- return length_;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::empty() const {
- return length_ == 0;
-}
-
-template <typename TChar>
-inline std::basic_string<TChar> BasicStringPiece<TChar>::to_string() const {
- return std::basic_string<TChar>(data_, length_);
-}
-
-template <>
-inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const {
- if (!data_ || !rhs.data_) {
- return false;
- }
- if (rhs.length_ > length_) {
- return false;
- }
- return strstr(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
- const char nullStr = '\0';
- const char* b1 = data_ != nullptr ? data_ : &nullStr;
- const char* e1 = b1 + length_;
- const char* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
- const char* e2 = b2 + rhs.length_;
-
- while (b1 < e1 && b2 < e2) {
- const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
- if (d) {
- return d;
- }
- }
- return static_cast<int>(length_ - rhs.length_);
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
- const ssize_t result_len = utf16_to_utf8_length(str.data(), str.size());
- if (result_len < 0) {
- // Empty string.
- return out;
- }
-
- std::string result;
- result.resize(static_cast<size_t>(result_len));
- utf16_to_utf8(str.data(), str.length(), &*result.begin(), static_cast<size_t>(result_len) + 1);
- return out << result;
-}
-
-template <>
-inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const {
- if (!data_ || !rhs.data_) {
- return false;
- }
- if (rhs.length_ > length_) {
- return false;
- }
- return strstr16(data_, rhs.data_) != nullptr;
-}
-
-template <>
-inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
- const char16_t nullStr = u'\0';
- const char16_t* b1 = data_ != nullptr ? data_ : &nullStr;
- const char16_t* b2 = rhs.data_ != nullptr ? rhs.data_ : &nullStr;
- return strzcmp16(b1, length_, b2, rhs.length_);
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) < 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) > 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) == 0;
-}
-
-template <typename TChar>
-inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
- return compare(rhs) != 0;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
- return data_;
-}
-
-template <typename TChar>
-inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
- return data_ + length_;
-}
-
-template <typename TChar>
-inline bool operator==(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const TChar* lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
-inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
- return out.write(str.data(), str.size());
-}
-
-template <typename TChar>
-inline ::std::basic_string<TChar>& operator+=(::std::basic_string<TChar>& lhs,
- const BasicStringPiece<TChar>& rhs) {
- return lhs.append(rhs.data(), rhs.size());
-}
-
-template <typename TChar>
-inline bool operator==(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) == rhs;
-}
-
-template <typename TChar>
-inline bool operator!=(const ::std::basic_string<TChar>& lhs, const BasicStringPiece<TChar>& rhs) {
- return BasicStringPiece<TChar>(lhs) != rhs;
-}
-
} // namespace android
-inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
+namespace std {
+
+inline ::std::ostream& operator<<(::std::ostream& out, ::std::u16string_view str) {
ssize_t utf8_len = utf16_to_utf8_length(str.data(), str.size());
if (utf8_len < 0) {
- return out << "???";
+ return out; // empty
}
std::string utf8;
utf8.resize(static_cast<size_t>(utf8_len));
- utf16_to_utf8(str.data(), str.size(), &*utf8.begin(), utf8_len + 1);
+ utf16_to_utf8(str.data(), str.size(), utf8.data(), utf8_len + 1);
return out << utf8;
}
-namespace std {
-
-template <typename TChar>
-struct hash<android::BasicStringPiece<TChar>> {
- size_t operator()(const android::BasicStringPiece<TChar>& str) const {
- uint32_t hashCode = android::JenkinsHashMixBytes(
- 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size());
- return static_cast<size_t>(hashCode);
- }
-};
+inline ::std::ostream& operator<<(::std::ostream& out, const ::std::u16string& str) {
+ return out << std::u16string_view(str);
+}
} // namespace std
diff --git a/libs/androidfw/include/androidfw/StringPool.h b/libs/androidfw/include/androidfw/StringPool.h
index 25174d8..0190ab5 100644
--- a/libs/androidfw/include/androidfw/StringPool.h
+++ b/libs/androidfw/include/androidfw/StringPool.h
@@ -167,11 +167,11 @@
// Adds a string to the pool, unless it already exists. Returns a reference to the string in the
// pool.
- Ref MakeRef(const android::StringPiece& str);
+ Ref MakeRef(android::StringPiece str);
// Adds a string to the pool, unless it already exists, with a context object that can be used
// when sorting the string pool. Returns a reference to the string in the pool.
- Ref MakeRef(const android::StringPiece& str, const Context& context);
+ Ref MakeRef(android::StringPiece str, const Context& context);
// Adds a string from another string pool. Returns a reference to the string in the string pool.
Ref MakeRef(const Ref& ref);
@@ -215,7 +215,7 @@
static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8, IDiagnostics* diag);
- Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique);
+ Ref MakeRefImpl(android::StringPiece str, const Context& context, bool unique);
void ReAssignIndices();
std::vector<std::unique_ptr<Entry>> strings_;
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 1bbc7f5..ae7c65f 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -123,16 +123,16 @@
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out);
// Converts a UTF-8 string to a UTF-16 string.
-std::u16string Utf8ToUtf16(const StringPiece& utf8);
+std::u16string Utf8ToUtf16(StringPiece utf8);
// Converts a UTF-16 string to a UTF-8 string.
-std::string Utf16ToUtf8(const StringPiece16& utf16);
+std::string Utf16ToUtf8(StringPiece16 utf16);
// Converts a UTF8 string into Modified UTF8
-std::string Utf8ToModifiedUtf8(const std::string& utf8);
+std::string Utf8ToModifiedUtf8(std::string_view utf8);
// Converts a Modified UTF8 string into a UTF8 string
-std::string ModifiedUtf8ToUtf8(const std::string& modified_utf8);
+std::string ModifiedUtf8ToUtf8(std::string_view modified_utf8);
inline uint16_t HostToDevice16(uint16_t value) {
return htods(value);
@@ -150,7 +150,7 @@
return dtohl(value);
}
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
template <typename T>
inline bool IsFourByteAligned(const incfs::map_ptr<T>& data) {
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index 5a5a0e2..d40d24e 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -44,6 +44,10 @@
/* get the file's modification date; returns -1 w/errno set on failure */
time_t getFileModDate(const char* fileName);
+// Check if |path| or |fd| resides on a readonly filesystem.
+bool isReadonlyFilesystem(const char* path);
+bool isReadonlyFilesystem(int fd);
+
}; // namespace android
#endif // _LIBS_ANDROID_FW_MISC_H
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 5285420..7af5066 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -21,12 +21,17 @@
//
#include <androidfw/misc.h>
-#include <sys/stat.h>
-#include <cstring>
-#include <errno.h>
-#include <cstdio>
+#include "android-base/logging.h"
-using namespace android;
+#ifndef _WIN32
+#include <sys/statvfs.h>
+#include <sys/vfs.h>
+#endif // _WIN32
+
+#include <cstring>
+#include <cstdio>
+#include <errno.h>
+#include <sys/stat.h>
namespace android {
@@ -41,8 +46,7 @@
if (errno == ENOENT || errno == ENOTDIR)
return kFileTypeNonexistent;
else {
- fprintf(stderr, "getFileType got errno=%d on '%s'\n",
- errno, fileName);
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
return kFileTypeUnknown;
}
} else {
@@ -82,4 +86,32 @@
return sb.st_mtime;
}
+#ifdef _WIN32
+// No need to implement these for Windows, the functions only matter on a device.
+bool isReadonlyFilesystem(const char*) {
+ return false;
+}
+bool isReadonlyFilesystem(int) {
+ return false;
+}
+#else // _WIN32
+bool isReadonlyFilesystem(const char* path) {
+ struct statfs sfs;
+ if (::statfs(path, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+
+bool isReadonlyFilesystem(int fd) {
+ struct statfs sfs;
+ if (::fstatfs(fd, &sfs)) {
+ PLOG(ERROR) << "isReadonlyFilesystem(): fstatfs(" << fd << ") failed";
+ return false;
+ }
+ return (sfs.f_flags & ST_RDONLY) != 0;
+}
+#endif // _WIN32
+
}; // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
index ddd8ab8..1c89c61 100644
--- a/libs/androidfw/tests/AttributeResolution_bench.cpp
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -120,8 +120,8 @@
return;
}
- std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(layout_path->to_string(), value->cookie,
- Asset::ACCESS_BUFFER);
+ std::unique_ptr<Asset> asset =
+ assetmanager.OpenNonAsset(std::string(*layout_path), value->cookie, Asset::ACCESS_BUFFER);
if (asset == nullptr) {
state.SkipWithError("failed to load layout");
return;
diff --git a/libs/androidfw/tests/ByteBucketArray_test.cpp b/libs/androidfw/tests/ByteBucketArray_test.cpp
index 5d464c7..9c36cfb 100644
--- a/libs/androidfw/tests/ByteBucketArray_test.cpp
+++ b/libs/androidfw/tests/ByteBucketArray_test.cpp
@@ -52,4 +52,57 @@
}
}
+TEST(ByteBucketArrayTest, TestForEach) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(10, 2));
+ ASSERT_TRUE(bba.set(26, 3));
+ ASSERT_TRUE(bba.set(129, 4));
+ ASSERT_TRUE(bba.set(234, 5));
+
+ int count = 0;
+ bba.forEachItem([&count](auto i, auto val) {
+ ++count;
+ switch (i) {
+ case 0:
+ EXPECT_EQ(1, val);
+ break;
+ case 10:
+ EXPECT_EQ(2, val);
+ break;
+ case 26:
+ EXPECT_EQ(3, val);
+ break;
+ case 129:
+ EXPECT_EQ(4, val);
+ break;
+ case 234:
+ EXPECT_EQ(5, val);
+ break;
+ default:
+ EXPECT_EQ(0, val);
+ break;
+ }
+ });
+ ASSERT_EQ(4 * 16, count);
+}
+
+TEST(ByteBucketArrayTest, TestTrimBuckets) {
+ ByteBucketArray<int> bba;
+ ASSERT_TRUE(bba.set(0, 1));
+ ASSERT_TRUE(bba.set(255, 2));
+ {
+ bba.trimBuckets([](auto val) { return val < 2; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(1 * 16, count);
+ }
+ {
+ bba.trimBuckets([](auto val) { return val < 3; });
+ int count = 0;
+ bba.forEachItem([&count](auto, auto) { ++count; });
+ ASSERT_EQ(0, count);
+ }
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index ce7f805..8fed0a4 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -25,8 +25,8 @@
namespace android {
-static ::testing::AssertionResult TestParse(
- const StringPiece& input, ConfigDescription* config = nullptr) {
+static ::testing::AssertionResult TestParse(StringPiece input,
+ ConfigDescription* config = nullptr) {
if (ConfigDescription::Parse(input, config)) {
return ::testing::AssertionSuccess() << input << " was successfully parsed";
}
@@ -138,7 +138,7 @@
EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
}
-static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+static inline ConfigDescription ParseConfigOrDie(android::StringPiece str) {
ConfigDescription config;
CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
return config;
diff --git a/libs/androidfw/tests/StringPiece_test.cpp b/libs/androidfw/tests/StringPiece_test.cpp
index 316a5c1..822e527 100644
--- a/libs/androidfw/tests/StringPiece_test.cpp
+++ b/libs/androidfw/tests/StringPiece_test.cpp
@@ -60,36 +60,4 @@
EXPECT_TRUE(StringPiece(car) > banana);
}
-TEST(StringPieceTest, ContainsOtherStringPiece) {
- StringPiece text("I am a leaf on the wind.");
- StringPiece start_needle("I am");
- StringPiece end_needle("wind.");
- StringPiece middle_needle("leaf");
- StringPiece empty_needle("");
- StringPiece missing_needle("soar");
- StringPiece long_needle("This string is longer than the text.");
-
- EXPECT_TRUE(text.contains(start_needle));
- EXPECT_TRUE(text.contains(end_needle));
- EXPECT_TRUE(text.contains(middle_needle));
- EXPECT_TRUE(text.contains(empty_needle));
- EXPECT_FALSE(text.contains(missing_needle));
- EXPECT_FALSE(text.contains(long_needle));
-
- StringPiece16 text16(u"I am a leaf on the wind.");
- StringPiece16 start_needle16(u"I am");
- StringPiece16 end_needle16(u"wind.");
- StringPiece16 middle_needle16(u"leaf");
- StringPiece16 empty_needle16(u"");
- StringPiece16 missing_needle16(u"soar");
- StringPiece16 long_needle16(u"This string is longer than the text.");
-
- EXPECT_TRUE(text16.contains(start_needle16));
- EXPECT_TRUE(text16.contains(end_needle16));
- EXPECT_TRUE(text16.contains(middle_needle16));
- EXPECT_TRUE(text16.contains(empty_needle16));
- EXPECT_FALSE(text16.contains(missing_needle16));
- EXPECT_FALSE(text16.contains(long_needle16));
-}
-
} // namespace android
diff --git a/libs/androidfw/tests/StringPool_test.cpp b/libs/androidfw/tests/StringPool_test.cpp
index 047d457..0e0acae 100644
--- a/libs/androidfw/tests/StringPool_test.cpp
+++ b/libs/androidfw/tests/StringPool_test.cpp
@@ -321,15 +321,15 @@
ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR);
auto str = test.string8At(0);
ASSERT_TRUE(str.has_value());
- EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80"));
+ EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80"));
str = test.string8At(1);
ASSERT_TRUE(str.has_value());
- EXPECT_THAT(str->to_string(), Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
+ EXPECT_THAT(*str, Eq("foo \xED\xA0\x81\xED\xB0\xB7 bar"));
str = test.string8At(2);
ASSERT_TRUE(str.has_value());
- EXPECT_THAT(str->to_string(), Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
+ EXPECT_THAT(*str, Eq("\xED\xA0\x81\xED\xB0\x80\xED\xA0\x81\xED\xB0\xB7"));
// Check that retrieving the strings returns the original UTF-8 character bytes
EXPECT_THAT(android::util::GetString(test, 0), Eq("\xF0\x90\x90\x80"));
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index c0a4fdf..88cfed9 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -589,6 +589,7 @@
"ProfileData.cpp",
"ProfileDataContainer.cpp",
"Readback.cpp",
+ "Tonemapper.cpp",
"TreeInfo.cpp",
"WebViewFunctorManager.cpp",
"protos/graphicsstats.proto",
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 02c2e67..8dcd6db 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -16,16 +16,6 @@
#include "Readback.h"
-#include <sync/sync.h>
-#include <system/window.h>
-
-#include <gui/TraceUtils.h>
-#include "DeferredLayerUpdater.h"
-#include "Properties.h"
-#include "hwui/Bitmap.h"
-#include "pipeline/skia/LayerDrawable.h"
-#include "renderthread/EglManager.h"
-#include "renderthread/VulkanManager.h"
#include <SkBitmap.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
@@ -38,6 +28,19 @@
#include <SkRefCnt.h>
#include <SkSamplingOptions.h>
#include <SkSurface.h>
+#include <gui/TraceUtils.h>
+#include <private/android/AHardwareBufferHelpers.h>
+#include <shaders/shaders.h>
+#include <sync/sync.h>
+#include <system/window.h>
+
+#include "DeferredLayerUpdater.h"
+#include "Properties.h"
+#include "Tonemapper.h"
+#include "hwui/Bitmap.h"
+#include "pipeline/skia/LayerDrawable.h"
+#include "renderthread/EglManager.h"
+#include "renderthread/VulkanManager.h"
#include "utils/Color.h"
#include "utils/MathUtils.h"
#include "utils/NdkUtils.h"
@@ -91,8 +94,18 @@
}
}
- sk_sp<SkColorSpace> colorSpace = DataSpaceToColorSpace(
- static_cast<android_dataspace>(ANativeWindow_getBuffersDataSpace(window)));
+ int32_t dataspace = ANativeWindow_getBuffersDataSpace(window);
+
+ // If the application is not updating the Surface themselves, e.g., another
+ // process is producing buffers for the application to display, then
+ // ANativeWindow_getBuffersDataSpace will return an unknown answer, so grab
+ // the dataspace from buffer metadata instead, if it exists.
+ if (dataspace == 0) {
+ dataspace = AHardwareBuffer_getDataSpace(sourceBuffer.get());
+ }
+
+ sk_sp<SkColorSpace> colorSpace =
+ DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
sk_sp<SkImage> image =
SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
@@ -227,6 +240,10 @@
const bool hasBufferCrop = cropRect.left < cropRect.right && cropRect.top < cropRect.bottom;
auto constraint =
hasBufferCrop ? SkCanvas::kStrict_SrcRectConstraint : SkCanvas::kFast_SrcRectConstraint;
+
+ static constexpr float kMaxLuminanceNits = 4000.f;
+ tonemapPaint(image->imageInfo(), canvas->imageInfo(), kMaxLuminanceNits, paint);
+
canvas->drawImageRect(image, imageSrcRect, imageDstRect, sampling, &paint, constraint);
canvas->restore();
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index 0695dd1..153c3b6 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -17,6 +17,8 @@
#include "SkiaInterpolator.h"
#include "include/core/SkMath.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkTypes.h"
#include "include/private/SkFixed.h"
#include "include/private/SkMalloc.h"
#include "include/private/SkTo.h"
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
new file mode 100644
index 0000000..a7e76b6
--- /dev/null
+++ b/libs/hwui/Tonemapper.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright 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.
+ */
+
+#include "Tonemapper.h"
+
+#include <SkRuntimeEffect.h>
+#include <log/log.h>
+#include <shaders/shaders.h>
+
+#include "utils/Color.h"
+
+namespace android::uirenderer {
+
+namespace {
+
+class ColorFilterRuntimeEffectBuilder : public SkRuntimeEffectBuilder {
+public:
+ explicit ColorFilterRuntimeEffectBuilder(sk_sp<SkRuntimeEffect> effect)
+ : SkRuntimeEffectBuilder(std::move(effect)) {}
+
+ sk_sp<SkColorFilter> makeColorFilter() {
+ return this->effect()->makeColorFilter(this->uniforms());
+ }
+};
+
+static sk_sp<SkColorFilter> createLinearEffectColorFilter(const shaders::LinearEffect& linearEffect,
+ float maxDisplayLuminance,
+ float currentDisplayLuminanceNits,
+ float maxLuminance) {
+ auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
+ auto [runtimeEffect, error] = SkRuntimeEffect::MakeForColorFilter(std::move(shaderString));
+ if (!runtimeEffect) {
+ LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
+ }
+
+ ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
+
+ const auto uniforms =
+ shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+ currentDisplayLuminanceNits, maxLuminance);
+
+ for (const auto& uniform : uniforms) {
+ effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
+ }
+
+ return effectBuilder.makeColorFilter();
+}
+
+static bool extractTransfer(ui::Dataspace dataspace) {
+ return dataspace & HAL_DATASPACE_TRANSFER_MASK;
+}
+
+static bool isHdrDataspace(ui::Dataspace dataspace) {
+ const auto transfer = extractTransfer(dataspace);
+
+ return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
+}
+
+static ui::Dataspace getDataspace(const SkImageInfo& image) {
+ return static_cast<ui::Dataspace>(
+ ColorSpaceToADataSpace(image.colorSpace(), image.colorType()));
+}
+
+} // namespace
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint) {
+ const auto sourceDataspace = getDataspace(source);
+ const auto destinationDataspace = getDataspace(destination);
+
+ if (extractTransfer(sourceDataspace) != extractTransfer(destinationDataspace) &&
+ (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace))) {
+ const auto effect = shaders::LinearEffect{
+ .inputDataspace = sourceDataspace,
+ .outputDataspace = destinationDataspace,
+ .undoPremultipliedAlpha = source.alphaType() == kPremul_SkAlphaType,
+ .fakeInputDataspace = destinationDataspace,
+ .type = shaders::LinearEffect::SkSLType::ColorFilter};
+ constexpr float kMaxDisplayBrightnessNits = 1000.f;
+ constexpr float kCurrentDisplayBrightnessNits = 500.f;
+ sk_sp<SkColorFilter> colorFilter = createLinearEffectColorFilter(
+ effect, kMaxDisplayBrightnessNits, kCurrentDisplayBrightnessNits, maxLuminanceNits);
+
+ if (paint.getColorFilter()) {
+ paint.setColorFilter(SkColorFilters::Compose(paint.refColorFilter(), colorFilter));
+ } else {
+ paint.setColorFilter(colorFilter);
+ }
+ }
+}
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/Tonemapper.h b/libs/hwui/Tonemapper.h
new file mode 100644
index 0000000..c0d5325
--- /dev/null
+++ b/libs/hwui/Tonemapper.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright 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.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+
+namespace android::uirenderer {
+
+// Given a source and destination image info, and the max content luminance, generate a tonemaping
+// shader and tag it on the supplied paint.
+void tonemapPaint(const SkImageInfo& source, const SkImageInfo& destination, float maxLuminanceNits,
+ SkPaint& paint);
+
+} // namespace android::uirenderer
diff --git a/libs/hwui/pipeline/skia/LayerDrawable.cpp b/libs/hwui/pipeline/skia/LayerDrawable.cpp
index 3ba5409..99f54c1 100644
--- a/libs/hwui/pipeline/skia/LayerDrawable.cpp
+++ b/libs/hwui/pipeline/skia/LayerDrawable.cpp
@@ -25,6 +25,7 @@
#include "SkColorFilter.h"
#include "SkRuntimeEffect.h"
#include "SkSurface.h"
+#include "Tonemapper.h"
#include "gl/GrGLTypes.h"
#include "math/mat4.h"
#include "system/graphics-base-v1.0.h"
@@ -76,37 +77,6 @@
isIntegerAligned(dstDevRect.y()));
}
-static sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> shader,
- const shaders::LinearEffect& linearEffect,
- float maxDisplayLuminance,
- float currentDisplayLuminanceNits,
- float maxLuminance) {
- auto shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
- auto [runtimeEffect, error] = SkRuntimeEffect::MakeForShader(std::move(shaderString));
- if (!runtimeEffect) {
- LOG_ALWAYS_FATAL("LinearColorFilter construction error: %s", error.c_str());
- }
-
- SkRuntimeShaderBuilder effectBuilder(std::move(runtimeEffect));
-
- effectBuilder.child("child") = std::move(shader);
-
- const auto uniforms = shaders::buildLinearEffectUniforms(
- linearEffect, mat4(), maxDisplayLuminance, currentDisplayLuminanceNits, maxLuminance);
-
- for (const auto& uniform : uniforms) {
- effectBuilder.uniform(uniform.name.c_str()).set(uniform.value.data(), uniform.value.size());
- }
-
- return effectBuilder.makeShader();
-}
-
-static bool isHdrDataspace(ui::Dataspace dataspace) {
- const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
-}
-
static void adjustCropForYUV(uint32_t format, int bufferWidth, int bufferHeight, SkRect* cropRect) {
// Chroma channels of YUV420 images are subsampled we may need to shrink the crop region by
// a whole texel on each side. Since skia still adds its own 0.5 inset, we apply an
@@ -215,31 +185,10 @@
sampling = SkSamplingOptions(SkFilterMode::kLinear);
}
- const auto sourceDataspace = static_cast<ui::Dataspace>(
- ColorSpaceToADataSpace(layerImage->colorSpace(), layerImage->colorType()));
- const SkImageInfo& imageInfo = canvas->imageInfo();
- const auto destinationDataspace = static_cast<ui::Dataspace>(
- ColorSpaceToADataSpace(imageInfo.colorSpace(), imageInfo.colorType()));
-
- if (isHdrDataspace(sourceDataspace) || isHdrDataspace(destinationDataspace)) {
- const auto effect = shaders::LinearEffect{
- .inputDataspace = sourceDataspace,
- .outputDataspace = destinationDataspace,
- .undoPremultipliedAlpha = layerImage->alphaType() == kPremul_SkAlphaType,
- .fakeInputDataspace = destinationDataspace};
- auto shader = layerImage->makeShader(sampling,
- SkMatrix::RectToRect(skiaSrcRect, skiaDestRect));
- constexpr float kMaxDisplayBrightess = 1000.f;
- constexpr float kCurrentDisplayBrightness = 500.f;
- shader = createLinearEffectShader(std::move(shader), effect, kMaxDisplayBrightess,
- kCurrentDisplayBrightness,
- layer->getMaxLuminanceNits());
- paint.setShader(shader);
- canvas->drawRect(skiaDestRect, paint);
- } else {
- canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
- constraint);
- }
+ tonemapPaint(layerImage->imageInfo(), canvas->imageInfo(), layer->getMaxLuminanceNits(),
+ paint);
+ canvas->drawImageRect(layerImage.get(), skiaSrcRect, skiaDestRect, sampling, &paint,
+ constraint);
canvas->restore();
// restore the original matrix
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index 529eddd..5acec79 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -101,6 +101,15 @@
public static final String ACTION_FUSED_PROVIDER =
"com.android.location.service.FusedLocationProvider";
+ /**
+ * The action the wrapping service should have in its intent filter to implement the
+ * {@link android.location.LocationManager#GPS_PROVIDER}.
+ *
+ * @hide
+ */
+ public static final String ACTION_GNSS_PROVIDER =
+ "android.location.provider.action.GNSS_PROVIDER";
+
final String mTag;
final @Nullable String mAttributionTag;
final IBinder mBinder;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f3931df..9c5313a 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -7670,8 +7670,10 @@
* or video calls. This method can be used by voice or video chat applications to select a
* different audio device than the one selected by default by the platform.
* <p>The device selection is expressed as an {@link AudioDeviceInfo} among devices returned by
- * {@link #getAvailableCommunicationDevices()}.
- * The selection is active as long as the requesting application process lives, until
+ * {@link #getAvailableCommunicationDevices()}. Note that only devices in a sink role
+ * (AKA output devices, see {@link AudioDeviceInfo#isSink()}) can be specified. The matching
+ * source device is selected automatically by the platform.
+ * <p>The selection is active as long as the requesting application process lives, until
* {@link #clearCommunicationDevice} is called or until the device is disconnected.
* It is therefore important for applications to clear the request when a call ends or the
* the requesting activity or service is stopped or destroyed.
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 8594ba5..f1b1d79 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -34,6 +34,7 @@
cc_defaults {
name: "libandroid_defaults",
+ cpp_std: "gnu++20",
cflags: [
"-Wall",
"-Werror",
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 7863a7d..40eb507 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -71,8 +71,10 @@
const int64_t mPreferredRateNanos;
// Target duration for choosing update rate
int64_t mTargetDurationNanos;
- // Last update timestamp
- int64_t mLastUpdateTimestamp;
+ // First target hit timestamp
+ int64_t mFirstTargetMetTimestamp;
+ // Last target hit timestamp
+ int64_t mLastTargetMetTimestamp;
// Cached samples
std::vector<int64_t> mActualDurationsNanos;
std::vector<int64_t> mTimestampsNanos;
@@ -144,7 +146,8 @@
: mHintSession(std::move(session)),
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
- mLastUpdateTimestamp(elapsedRealtimeNano()) {}
+ mFirstTargetMetTimestamp(0),
+ mLastTargetMetTimestamp(0) {}
APerformanceHintSession::~APerformanceHintSession() {
binder::Status ret = mHintSession->close();
@@ -171,7 +174,8 @@
*/
mActualDurationsNanos.clear();
mTimestampsNanos.clear();
- mLastUpdateTimestamp = elapsedRealtimeNano();
+ mFirstTargetMetTimestamp = 0;
+ mLastTargetMetTimestamp = 0;
return 0;
}
@@ -184,25 +188,38 @@
mActualDurationsNanos.push_back(actualDurationNanos);
mTimestampsNanos.push_back(now);
- /**
- * Cache the hint if the hint is not overtime and the mLastUpdateTimestamp is
- * still in the mPreferredRateNanos duration.
- */
- if (actualDurationNanos < mTargetDurationNanos &&
- now - mLastUpdateTimestamp <= mPreferredRateNanos) {
- return 0;
+ if (actualDurationNanos >= mTargetDurationNanos) {
+ // Reset timestamps if we are equal or over the target.
+ mFirstTargetMetTimestamp = 0;
+ } else {
+ // Set mFirstTargetMetTimestamp for first time meeting target.
+ if (!mFirstTargetMetTimestamp || !mLastTargetMetTimestamp ||
+ (now - mLastTargetMetTimestamp > 2 * mPreferredRateNanos)) {
+ mFirstTargetMetTimestamp = now;
+ }
+ /**
+ * Rate limit the change if the update is over mPreferredRateNanos since first
+ * meeting target and less than mPreferredRateNanos since last meeting target.
+ */
+ if (now - mFirstTargetMetTimestamp > mPreferredRateNanos &&
+ now - mLastTargetMetTimestamp <= mPreferredRateNanos) {
+ return 0;
+ }
+ mLastTargetMetTimestamp = now;
}
binder::Status ret =
mHintSession->reportActualWorkDuration(mActualDurationsNanos, mTimestampsNanos);
- mActualDurationsNanos.clear();
- mTimestampsNanos.clear();
if (!ret.isOk()) {
ALOGE("%s: HintSession reportActualWorkDuration failed: %s", __FUNCTION__,
ret.exceptionMessage().c_str());
+ mFirstTargetMetTimestamp = 0;
+ mLastTargetMetTimestamp = 0;
return EPIPE;
}
- mLastUpdateTimestamp = now;
+ mActualDurationsNanos.clear();
+ mTimestampsNanos.clear();
+
return 0;
}
diff --git a/native/android/system_fonts.cpp b/native/android/system_fonts.cpp
index 30d0c35..fe3132e 100644
--- a/native/android/system_fonts.cpp
+++ b/native/android/system_fonts.cpp
@@ -66,9 +66,6 @@
return mFilePath == o.mFilePath && mLocale == o.mLocale && mWeight == o.mWeight &&
mItalic == o.mItalic && mCollectionIndex == o.mCollectionIndex && mAxes == o.mAxes;
}
-
- AFont() = default;
- AFont(const AFont&) = default;
};
struct FontHasher {
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 252ecf4..8c9023c 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -49,6 +49,8 @@
<string name="save_password_to_title">Save password to</string>
<!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
<string name="save_sign_in_to_title">Save sign-in to</string>
+ <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
+ <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
<!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
<string name="use_provider_for_all_title">Use <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g> for all your sign-ins?</string>
<!-- TODO: Check the wording here. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 530f1c4..e3ed3d9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -44,6 +44,8 @@
import com.android.credentialmanager.createflow.CreateCredentialUiState
import com.android.credentialmanager.createflow.CreateScreenState
import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.RemoteInfo
+import com.android.credentialmanager.createflow.RequestDisplayInfo
import com.android.credentialmanager.getflow.GetCredentialUiState
import com.android.credentialmanager.getflow.GetScreenState
import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
@@ -57,7 +59,7 @@
) {
val requestInfo: RequestInfo
private val providerEnabledList: List<ProviderData>
- private val providerDisabledList: List<DisabledProviderData>
+ private val providerDisabledList: List<DisabledProviderData>?
// TODO: require non-null.
val resultReceiver: ResultReceiver?
@@ -141,26 +143,23 @@
providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
// Handle runtime cast error
- providerDisabledList as List<DisabledProviderData>, context)
- var hasDefault = false
- var defaultProvider: EnabledProviderInfo = providerEnabledList.first()
+ providerDisabledList, context)
+ var defaultProvider: EnabledProviderInfo? = null
+ var remoteEntry: RemoteInfo? = null
providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
- if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
+ if (providerInfo.isDefault) {defaultProvider = providerInfo}
+ if (providerInfo.remoteEntry != null) {
+ remoteEntry = providerInfo.remoteEntry!!
+ }
+ }
return CreateCredentialUiState(
enabledProviders = providerEnabledList,
disabledProviders = providerDisabledList,
- // TODO: Add the screen when defaultProvider has no createOption but
- // there's remoteInfo under other providers
- if (!hasDefault || defaultProvider.createOptions.isEmpty()) {
- if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL)
- {CreateScreenState.PASSKEY_INTRO} else {CreateScreenState.PROVIDER_SELECTION}
- } else {CreateScreenState.CREATION_OPTION_SELECTION},
+ toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
requestDisplayInfo,
false,
- if (hasDefault) {
- ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
- } else null
+ toActiveEntry(defaultProvider, remoteEntry),
)
}
@@ -210,7 +209,7 @@
)
}
- private fun testDisabledProviderList(): List<DisabledProviderData> {
+ private fun testDisabledProviderList(): List<DisabledProviderData>? {
return listOf(
DisabledProviderData("com.lastpass.lpandroid"),
DisabledProviderData("com.google.android.youtube")
@@ -459,12 +458,15 @@
" \"residentKey\": \"required\",\n" +
" \"requireResidentKey\": true\n" +
" }}")
- val data = request.data
+ val credentialData = request.data
return RequestInfo.newCreateRequestInfo(
Binder(),
CreateCredentialRequest(
TYPE_PUBLIC_KEY_CREDENTIAL,
- data
+ credentialData,
+ // TODO: populate with actual data
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -477,7 +479,10 @@
Binder(),
CreateCredentialRequest(
TYPE_PASSWORD_CREDENTIAL,
- data
+ data,
+ // TODO: populate with actual data
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -490,7 +495,9 @@
Binder(),
CreateCredentialRequest(
"other-sign-ins",
- data
+ data,
+ /*candidateQueryData=*/ Bundle(),
+ /*requireSystemProvider=*/ false
),
/*isFirstUsage=*/false,
"tribank"
@@ -502,11 +509,46 @@
Binder(),
GetCredentialRequest.Builder()
.addGetCredentialOption(
- GetCredentialOption(TYPE_PUBLIC_KEY_CREDENTIAL, Bundle())
+ GetCredentialOption(
+ TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
)
.build(),
/*isFirstUsage=*/false,
"tribank.us"
)
}
+
+ private fun toCreateScreenState(
+ requestDisplayInfo: RequestDisplayInfo,
+ defaultProvider: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): CreateScreenState {
+ return if (
+ defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
+ ){
+ CreateScreenState.EXTERNAL_ONLY_SELECTION
+ } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
+ if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
+ CreateScreenState.PASSKEY_INTRO
+ } else {
+ CreateScreenState.PROVIDER_SELECTION
+ }
+ } else {
+ CreateScreenState.CREATION_OPTION_SELECTION
+ }
+ }
+
+ private fun toActiveEntry(
+ defaultProvider: EnabledProviderInfo?,
+ remoteEntry: RemoteInfo?,
+ ): ActiveEntry? {
+ return if (
+ defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+ ) {
+ ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+ } else if (
+ defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+ ActiveEntry(defaultProvider, remoteEntry)
+ } else null
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index b96f686..357c55d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -209,12 +209,12 @@
}
fun toDisabledProviderList(
- providerDataList: List<DisabledProviderData>,
+ providerDataList: List<DisabledProviderData>?,
context: Context,
- ): List<com.android.credentialmanager.createflow.DisabledProviderInfo> {
+ ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
// TODO: get from the actual service info
val packageManager = context.packageManager
- return providerDataList.map {
+ return providerDataList?.map {
val pkgInfo = packageManager
.getPackageInfo(it.providerFlattenedComponentName,
PackageManager.PackageInfoFlags.of(0))
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index fde3279..5552d05 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -60,7 +60,7 @@
viewModel.onEntrySelected(it, providerActivityLauncher)
}
val confirmEntryCallback: () -> Unit = {
- viewModel.onConfirmCreationSelected(providerActivityLauncher)
+ viewModel.onConfirmEntrySelected(providerActivityLauncher)
}
val state = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Expanded,
@@ -108,9 +108,16 @@
providerInfo = uiState.activeEntry?.activeProvider!!,
onDefaultOrNotSelected = viewModel::onDefaultOrNotSelected
)
+ CreateScreenState.EXTERNAL_ONLY_SELECTION -> ExternalOnlySelectionCard(
+ requestDisplayInfo = uiState.requestDisplayInfo,
+ activeRemoteEntry = uiState.activeEntry?.activeEntryInfo!!,
+ onOptionSelected = selectEntryCallback,
+ onConfirm = confirmEntryCallback,
+ onCancel = viewModel::onCancel,
+ )
}
},
- scrimColor = MaterialTheme.colorScheme.scrim,
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
@@ -489,7 +496,7 @@
) {
PrimaryCreateOptionRow(
requestDisplayInfo = requestDisplayInfo,
- createOptionInfo = createOptionInfo,
+ entryInfo = createOptionInfo,
onOptionSelected = onOptionSelected
)
}
@@ -562,16 +569,85 @@
@OptIn(ExperimentalMaterial3Api::class)
@Composable
+fun ExternalOnlySelectionCard(
+ requestDisplayInfo: RequestDisplayInfo,
+ activeRemoteEntry: EntryInfo,
+ onOptionSelected: (EntryInfo) -> Unit,
+ onConfirm: () -> Unit,
+ onCancel: () -> Unit,
+) {
+ Card() {
+ Column() {
+ Icon(
+ painter = painterResource(R.drawable.ic_other_devices),
+ contentDescription = null,
+ tint = Color.Unspecified,
+ modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
+ .padding(all = 24.dp).size(32.dp)
+ )
+ Text(
+ text = stringResource(R.string.create_passkey_in_other_device_title),
+ style = MaterialTheme.typography.titleMedium,
+ modifier = Modifier.padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ textAlign = TextAlign.Center,
+ )
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Card(
+ shape = MaterialTheme.shapes.medium,
+ modifier = Modifier
+ .padding(horizontal = 24.dp)
+ .align(alignment = Alignment.CenterHorizontally),
+ ) {
+ PrimaryCreateOptionRow(
+ requestDisplayInfo = requestDisplayInfo,
+ entryInfo = activeRemoteEntry,
+ onOptionSelected = onOptionSelected
+ )
+ }
+ Divider(
+ thickness = 24.dp,
+ color = Color.Transparent
+ )
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+ ) {
+ CancelButton(
+ stringResource(R.string.string_cancel),
+ onClick = onCancel
+ )
+ ConfirmButton(
+ stringResource(R.string.string_continue),
+ onClick = onConfirm
+ )
+ }
+ Divider(
+ thickness = 18.dp,
+ color = Color.Transparent,
+ modifier = Modifier.padding(bottom = 16.dp)
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
fun PrimaryCreateOptionRow(
requestDisplayInfo: RequestDisplayInfo,
- createOptionInfo: CreateOptionInfo,
+ entryInfo: EntryInfo,
onOptionSelected: (EntryInfo) -> Unit
) {
Entry(
- onClick = {onOptionSelected(createOptionInfo)},
+ onClick = {onOptionSelected(entryInfo)},
icon = {
Icon(
- bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
+ bitmap = if (entryInfo is CreateOptionInfo) {
+ entryInfo.profileIcon.toBitmap().asImageBitmap()
+ } else {requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()},
contentDescription = null,
tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
modifier = Modifier.padding(start = 18.dp).size(32.dp)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 0f685a1..393cf7d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -155,7 +155,7 @@
}
}
- fun onConfirmCreationSelected(
+ fun onConfirmEntrySelected(
launcher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>
) {
val selectedEntry = uiState.activeEntry?.activeEntryInfo
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 31d0365..9ac524a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -95,4 +95,5 @@
CREATION_OPTION_SELECTION,
MORE_OPTIONS_SELECTION,
MORE_OPTIONS_ROW_INTRO,
+ EXTERNAL_ONLY_SELECTION,
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 8ccdf4c..720f231 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -61,6 +61,7 @@
import com.android.credentialmanager.common.ui.Entry
import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+import com.android.credentialmanager.ui.theme.EntryShape
@Composable
fun GetCredentialScreen(
@@ -94,8 +95,8 @@
)
}
},
- scrimColor = Color.Transparent,
- sheetShape = MaterialTheme.shapes.medium,
+ scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+ sheetShape = EntryShape.TopRoundedCorner,
) {}
LaunchedEffect(state.currentValue) {
if (state.currentValue == ModalBottomSheetValue.Hidden) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
index 7e7dbde..008e1b6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -38,14 +38,18 @@
return try {
when (from.type) {
Credential.TYPE_PASSWORD_CREDENTIAL ->
- CreatePasswordRequest.createFrom(from.data)
+ CreatePasswordRequest.createFrom(from.credentialData)
PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
- CreatePublicKeyCredentialBaseRequest.createFrom(from.data)
+ CreatePublicKeyCredentialBaseRequest.createFrom(from.credentialData)
else ->
- CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ CreateCredentialRequest(
+ from.type, from.credentialData, from.requireSystemProvider()
+ )
}
} catch (e: FrameworkClassParsingException) {
- CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+ CreateCredentialRequest(
+ from.type, from.credentialData, from.requireSystemProvider()
+ )
}
}
}
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 71c52d9..37d6b42 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -36,7 +36,7 @@
</activity>
<provider
- android:name="com.android.settingslib.spa.framework.SpaSearchProvider"
+ android:name="com.android.settingslib.spa.search.SpaSearchProvider"
android:authorities="com.android.spa.gallery.search.provider"
android:enabled="true"
android:exported="false">
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index 0c9a043..238204a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -36,6 +36,7 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.createIntent
import com.android.settingslib.spa.gallery.R
import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.ASYNC_PREFERENCE_SUMMARY
@@ -91,6 +92,7 @@
spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
}
+ .setStatusDataFn { EntryStatusData(isDisabled = false) }
.build()
)
entryList.add(
@@ -103,6 +105,7 @@
searchKeywords = SIMPLE_PREFERENCE_KEYWORDS,
)
}
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
.build()
)
entryList.add(singleLineSummaryEntry())
@@ -269,7 +272,7 @@
)
}
.setSliceDataFn { sliceUri, _ ->
- val intent = owner.createBrowseIntent()?.createBrowsePendingIntent()
+ val intent = owner.createIntent()?.createBrowsePendingIntent()
?: return@setSliceDataFn null
return@setSliceDataFn object : EntrySliceData() {
init {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 238268a..f7cbdae 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -39,7 +39,10 @@
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.slice.appendSliceParams
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
import com.android.settingslib.spa.slice.presenter.SliceDemo
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
@@ -158,14 +161,13 @@
remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
for (entry in allSliceEntry) {
- SliceDemo(sliceUri = entry.createSliceUri(authority))
+ SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
}
}
}
@Composable
fun OnePage(arguments: Bundle?) {
- val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "")
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
@@ -176,8 +178,8 @@
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
override val title = "open page"
- override val enabled =
- page.isBrowsable(context, spaEnvironment.browseActivityClass).toState()
+ override val enabled = (spaEnvironment.browseActivityClass != null &&
+ page.isBrowsable()).toState()
override val onClick = openPage(page)
})
EntryList(pageWithEntry.entries)
@@ -186,7 +188,6 @@
@Composable
fun OneEntry(arguments: Bundle?) {
- val context = LocalContext.current
val entryRepository by spaEnvironment.entryRepository
val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "")
val entry = entryRepository.getEntry(id)!!
@@ -194,9 +195,9 @@
RegularScaffold(title = "Entry - ${entry.debugBrief()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
- override val enabled =
- entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass)
- .toState()
+ override val enabled = (spaEnvironment.browseActivityClass != null &&
+ entry.containerPage().isBrowsable())
+ .toState()
override val onClick = openEntry(entry)
})
Text(text = entryContent)
@@ -219,7 +220,7 @@
private fun openPage(page: SettingsPage): (() -> Unit)? {
val context = LocalContext.current
val intent =
- page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null
+ page.createIntent(SESSION_BROWSE) ?: return null
val route = page.buildRoute()
return {
spaEnvironment.logger.message(
@@ -232,8 +233,7 @@
@Composable
private fun openEntry(entry: SettingsEntry): (() -> Unit)? {
val context = LocalContext.current
- val intent = entry.containerPage()
- .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
+ val intent = entry.createIntent(SESSION_SEARCH)
?: return null
val route = entry.containerPage().buildRoute()
return {
@@ -245,18 +245,6 @@
}
}
-private fun SettingsEntry.createSliceUri(
- authority: String?,
- runtimeArguments: Bundle? = null
-): Uri {
- if (authority == null) return Uri.EMPTY
- return Uri.Builder().scheme("content").authority(authority).appendSliceParams(
- route = this.containerPage().buildRoute(),
- entryId = this.id,
- runtimeArguments = runtimeArguments,
- ).build()
-}
-
/**
* A blank activity without any page.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 3df7727..59ec985 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -32,6 +32,12 @@
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.addUri
import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
+import com.android.settingslib.spa.framework.util.SESSION_BROWSE
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
private const val TAG = "DebugProvider"
@@ -116,9 +122,11 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns())
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
- val command = pageWithEntry.page.createBrowseAdbCommand(
- context,
- spaEnvironment.browseActivityClass
+ val page = pageWithEntry.page
+ if (!page.isBrowsable()) continue
+ val command = createBrowseAdbCommand(
+ destination = page.buildRoute(),
+ sessionName = SESSION_BROWSE
)
if (command != null) {
cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command)
@@ -131,8 +139,13 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- val command = entry.containerPage()
- .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id)
+ val page = entry.containerPage()
+ if (!page.isBrowsable()) continue
+ val command = createBrowseAdbCommand(
+ destination = page.buildRoute(),
+ entryId = entry.id,
+ sessionName = SESSION_SEARCH
+ )
if (command != null) {
cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command)
}
@@ -145,8 +158,7 @@
val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns())
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
val page = pageWithEntry.page
- val intent =
- page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent()
+ val intent = page.createIntent(SESSION_BROWSE) ?: Intent()
cursor.newRow()
.add(ColumnEnum.PAGE_ID.id, page.id)
.add(ColumnEnum.PAGE_NAME.id, page.displayName)
@@ -162,17 +174,36 @@
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
- val intent = entry.containerPage()
- .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id)
- ?: Intent()
+ val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
.add(ColumnEnum.ENTRY_NAME.id, entry.displayName)
.add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute())
.add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME))
- .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id,
- entryRepository.getEntryPathWithDisplayName(entry.id))
+ .add(
+ ColumnEnum.ENTRY_HIERARCHY_PATH.id,
+ entryRepository.getEntryPathWithDisplayName(entry.id)
+ )
}
return cursor
}
}
+
+private fun createBrowseAdbCommand(
+ destination: String? = null,
+ entryId: String? = null,
+ sessionName: String? = null,
+): String? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ val packageName = context.packageName
+ val activityName = browseActivityClass.name.replace(packageName, "")
+ val destinationParam =
+ if (destination != null) " -e $KEY_DESTINATION $destination" else ""
+ val highlightParam =
+ if (entryId != null) " -e $KEY_HIGHLIGHT_ENTRY $entryId" else ""
+ val sessionParam =
+ if (sessionName != null) " -e $KEY_SESSION_SOURCE_NAME $sessionName" else ""
+ return "adb shell am start -n $packageName/$activityName" +
+ "$destinationParam$highlightParam$sessionParam"
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 6ed7481..aa10cc8 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -16,21 +16,17 @@
package com.android.settingslib.spa.framework
+import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.core.view.WindowCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -39,12 +35,16 @@
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.framework.compose.LocalNavController
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.PageEvent
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
+import com.android.settingslib.spa.framework.util.getSessionName
import com.android.settingslib.spa.framework.util.navRoute
private const val TAG = "BrowseActivity"
@@ -77,57 +77,20 @@
setContent {
SettingsTheme {
val sppRepository by spaEnvironment.pageProviderRepository
- BrowseContent(
- allProviders = sppRepository.getAllProviders(),
- initialDestination = intent?.getStringExtra(KEY_DESTINATION)
- ?: sppRepository.getDefaultStartPage(),
- initialEntryId = intent?.getStringExtra(KEY_HIGHLIGHT_ENTRY)
- )
+ BrowseContent(sppRepository, intent)
}
}
}
-
- companion object {
- const val KEY_DESTINATION = "spaActivityDestination"
- const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
- }
}
@VisibleForTesting
@Composable
-fun BrowseContent(
- allProviders: Collection<SettingsPageProvider>,
- initialDestination: String,
- initialEntryId: String?
-) {
+fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) {
val navController = rememberNavController()
CompositionLocalProvider(navController.localNavController()) {
val controller = LocalNavController.current as NavControllerWrapperImpl
- controller.NavContent(allProviders)
- controller.InitialDestination(initialDestination, initialEntryId)
- }
-}
-
-@Composable
-private fun SettingsPageProvider.PageEvents(arguments: Bundle? = null) {
- val page = remember(arguments) { createSettingsPage(arguments) }
- val lifecycleOwner = LocalLifecycleOwner.current
- DisposableEffect(lifecycleOwner) {
- val observer = LifecycleEventObserver { _, event ->
- if (event == Lifecycle.Event.ON_START) {
- page.enterPage()
- } else if (event == Lifecycle.Event.ON_STOP) {
- page.leavePage()
- }
- }
-
- // Add the observer to the lifecycle
- lifecycleOwner.lifecycle.addObserver(observer)
-
- // When the effect leaves the Composition, remove the observer
- onDispose {
- lifecycleOwner.lifecycle.removeObserver(observer)
- }
+ controller.NavContent(sppRepository.getAllProviders())
+ controller.InitialDestination(initialIntent, sppRepository.getDefaultStartPage())
}
}
@@ -144,7 +107,7 @@
route = spp.name + spp.parameter.navRoute(),
arguments = spp.parameter,
) { navBackStackEntry ->
- spp.PageEvents(navBackStackEntry.arguments)
+ spp.PageEvent(navBackStackEntry.arguments)
spp.Page(navBackStackEntry.arguments)
}
}
@@ -153,17 +116,22 @@
@Composable
private fun NavControllerWrapperImpl.InitialDestination(
- destination: String,
- highlightEntryId: String?
+ initialIntent: Intent?,
+ defaultDestination: String
) {
val destinationNavigated = rememberSaveable { mutableStateOf(false) }
if (destinationNavigated.value) return
destinationNavigated.value = true
- if (destination.isEmpty()) return
+ val initialDestination = initialIntent?.getDestination() ?: defaultDestination
+ if (initialDestination.isEmpty()) return
+ val initialEntryId = initialIntent?.getEntryId()
+ val sessionSourceName = initialIntent?.getSessionName()
+
LaunchedEffect(Unit) {
- highlightId = highlightEntryId
- navController.navigate(destination) {
+ highlightId = initialEntryId
+ sessionName = sessionSourceName
+ navController.navigate(initialDestination) {
popUpTo(navController.graph.findStartDestination().id) {
inclusive = true
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
index 121c07f..61b46be 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.spa.framework.common
import android.content.UriMatcher
+import androidx.annotation.VisibleForTesting
/**
* Enum to define all column names in provider.
@@ -125,14 +126,17 @@
),
}
-internal fun QueryEnum.getColumns(): Array<String> {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getColumns(): Array<String> {
return columnNames.map { it.id }.toTypedArray()
}
-internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.getIndex(name: ColumnEnum): Int {
return columnNames.indexOf(name)
}
-internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
uriMatcher.addURI(authority, queryPath, queryMatchCode)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 9ee7f9e..702c075 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -219,11 +219,6 @@
return this
}
- fun setIsAllowSearch(isAllowSearch: Boolean): SettingsEntryBuilder {
- this.isAllowSearch = isAllowSearch
- return this
- }
-
fun setIsSearchDataDynamic(isDynamic: Boolean): SettingsEntryBuilder {
this.isSearchDataDynamic = isDynamic
return this
@@ -251,6 +246,13 @@
fun setSearchDataFn(fn: SearchDataGetter): SettingsEntryBuilder {
this.searchDataFn = fn
+ this.isAllowSearch = true
+ return this
+ }
+
+ fun clearSearchDataFn(): SettingsEntryBuilder {
+ this.searchDataFn = { null }
+ this.isAllowSearch = false
return this
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 82e05a5..7a39b73 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -16,13 +16,8 @@
package com.android.settingslib.spa.framework.common
-import android.app.Activity
-import android.content.ComponentName
-import android.content.Context
-import android.content.Intent
import android.os.Bundle
import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.BrowseActivity
import com.android.settingslib.spa.framework.util.isRuntimeParam
import com.android.settingslib.spa.framework.util.navLink
import com.android.settingslib.spa.framework.util.normalize
@@ -95,63 +90,8 @@
return false
}
- fun enterPage() {
- SpaEnvironmentFactory.instance.logger.event(
- id,
- LogEvent.PAGE_ENTER,
- category = LogCategory.FRAMEWORK,
- details = displayName,
- )
- }
-
- fun leavePage() {
- SpaEnvironmentFactory.instance.logger.event(
- id,
- LogEvent.PAGE_LEAVE,
- category = LogCategory.FRAMEWORK,
- details = displayName,
- )
- }
-
- fun createBrowseIntent(entryId: String? = null): Intent? {
- val context = SpaEnvironmentFactory.instance.appContext
- val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass
- return createBrowseIntent(context, browseActivityClass, entryId)
- }
-
- fun createBrowseIntent(
- context: Context?,
- browseActivityClass: Class<out Activity>?,
- entryId: String? = null
- ): Intent? {
- if (!isBrowsable(context, browseActivityClass)) return null
- return Intent().setComponent(ComponentName(context!!, browseActivityClass!!))
- .apply {
- putExtra(BrowseActivity.KEY_DESTINATION, buildRoute())
- if (entryId != null) {
- putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId)
- }
- }
- }
-
- fun createBrowseAdbCommand(
- context: Context?,
- browseActivityClass: Class<out Activity>?,
- entryId: String? = null
- ): String? {
- if (!isBrowsable(context, browseActivityClass)) return null
- val packageName = context!!.packageName
- val activityName = browseActivityClass!!.name.replace(packageName, "")
- val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}"
- val highlightParam =
- if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else ""
- return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam"
- }
-
- fun isBrowsable(context: Context?, browseActivityClass: Class<out Activity>?): Boolean {
- return context != null &&
- browseActivityClass != null &&
- !isCreateBy(NULL_PAGE_NAME) &&
+ fun isBrowsable(): Boolean {
+ return !isCreateBy(NULL_PAGE_NAME) &&
!hasRuntimeParam()
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 00a0362..6ecb7fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -16,6 +16,7 @@
package com.android.settingslib.spa.framework.common
+import android.os.Bundle
import android.util.Log
// Defines the category of the log, for quick filter
@@ -38,10 +39,13 @@
// Entry related events.
ENTRY_CLICK,
- ENTRY_SWITCH_ON,
- ENTRY_SWITCH_OFF,
+ ENTRY_SWITCH,
}
+internal const val LOG_DATA_DISPLAY_NAME = "name"
+internal const val LOG_DATA_SESSION_NAME = "session"
+internal const val LOG_DATA_SWITCH_STATUS = "switch"
+
/**
* The interface of logger in Spa
*/
@@ -54,7 +58,7 @@
id: String,
event: LogEvent,
category: LogCategory = LogCategory.DEFAULT,
- details: String? = null
+ extraData: Bundle = Bundle.EMPTY
) {
}
}
@@ -64,8 +68,8 @@
Log.d("SpaMsg-$category", "[$tag] $msg")
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
- val extraMsg = if (details == null) "" else " ($details)"
- Log.d("SpaEvent-$category", "[$id] $event$extraMsg")
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+ val extraMsg = extraData.toString().removeRange(0, 6)
+ Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
}
}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
index 382c498..eb2bffe 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavControllerWrapper.kt
@@ -29,7 +29,10 @@
fun navigateBack()
val highlightEntryId: String?
- get() = null
+ get() = null
+
+ val sessionSourceName: String?
+ get() = null
}
@Composable
@@ -63,6 +66,7 @@
private val onBackPressedDispatcher: OnBackPressedDispatcher?,
) : NavControllerWrapper {
var highlightId: String? = null
+ var sessionName: String? = null
override fun navigate(route: String) {
navController.navigate(route)
@@ -73,5 +77,8 @@
}
override val highlightEntryId: String?
- get() = highlightId
+ get() = highlightId
+
+ override val sessionSourceName: String?
+ get() = sessionName
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
similarity index 74%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 8d0a35c..1c88187 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/WidgetLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -16,17 +16,22 @@
package com.android.settingslib.spa.framework.util
+import android.os.Bundle
import androidx.compose.runtime.Composable
+import androidx.core.os.bundleOf
+import com.android.settingslib.spa.framework.common.LOG_DATA_SWITCH_STATUS
import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@Composable
-fun logEntryEvent(): (event: LogEvent) -> Unit {
- val entryId = LocalEntryDataProvider.current.entryId ?: return {}
- return {
- SpaEnvironmentFactory.instance.logger.event(entryId, it, category = LogCategory.VIEW)
+fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
+ val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+ return { event, extraData ->
+ SpaEnvironmentFactory.instance.logger.event(
+ entryId, event, category = LogCategory.VIEW, extraData = extraData
+ )
}
}
@@ -35,7 +40,7 @@
if (onClick == null) return null
val logEvent = logEntryEvent()
return {
- logEvent(LogEvent.ENTRY_CLICK)
+ logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
onClick()
}
}
@@ -45,8 +50,7 @@
if (onSwitch == null) return null
val logEvent = logEntryEvent()
return {
- val event = if (it) LogEvent.ENTRY_SWITCH_ON else LogEvent.ENTRY_SWITCH_OFF
- logEvent(event)
+ logEvent(LogEvent.ENTRY_SWITCH, bundleOf(LOG_DATA_SWITCH_STATUS to it))
onSwitch(it)
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
index d801840..97e3ac2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Flows.kt
@@ -27,6 +27,13 @@
import kotlinx.coroutines.flow.map
/**
+ * Returns a [Flow] whose values are a list which containing the results of applying the given
+ * [transform] function to each element in the original flow's list.
+ */
+inline fun <T, R> Flow<List<T>>.mapItem(crossinline transform: (T) -> R): Flow<List<R>> =
+ map { list -> list.map(transform) }
+
+/**
* Returns a [Flow] whose values are a list which containing the results of asynchronously applying
* the given [transform] function to each element in the original flow's list.
*/
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
new file mode 100644
index 0000000..a881254
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.os.bundleOf
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import com.android.settingslib.spa.framework.common.LOG_DATA_DISPLAY_NAME
+import com.android.settingslib.spa.framework.common.LOG_DATA_SESSION_NAME
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.LocalNavController
+
+@Composable
+internal fun SettingsPageProvider.PageEvent(arguments: Bundle? = null) {
+ val page = remember(arguments) { createSettingsPage(arguments) }
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val navController = LocalNavController.current
+ DisposableEffect(lifecycleOwner) {
+ val observer = LifecycleEventObserver { _, event ->
+ val logPageEvent: (event: LogEvent) -> Unit = {
+ SpaEnvironmentFactory.instance.logger.event(
+ id = page.id,
+ event = it,
+ category = LogCategory.FRAMEWORK,
+ extraData = bundleOf(
+ LOG_DATA_DISPLAY_NAME to page.displayName,
+ LOG_DATA_SESSION_NAME to navController.sessionSourceName,
+ )
+ )
+ }
+ if (event == Lifecycle.Event.ON_START) {
+ logPageEvent(LogEvent.PAGE_ENTER)
+ } else if (event == Lifecycle.Event.ON_STOP) {
+ logPageEvent(LogEvent.PAGE_LEAVE)
+ }
+ }
+
+ // Add the observer to the lifecycle
+ lifecycleOwner.lifecycle.addObserver(observer)
+
+ // When the effect leaves the Composition, remove the observer
+ onDispose {
+ lifecycleOwner.lifecycle.removeObserver(observer)
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
new file mode 100644
index 0000000..2c3c2e0
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SpaIntent.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.ComponentName
+import android.content.Intent
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+
+const val SESSION_BROWSE = "browse"
+const val SESSION_SEARCH = "search"
+const val SESSION_SLICE = "slice"
+
+const val KEY_DESTINATION = "spaActivityDestination"
+const val KEY_HIGHLIGHT_ENTRY = "highlightEntry"
+const val KEY_SESSION_SOURCE_NAME = "sessionSource"
+
+val SPA_INTENT_RESERVED_KEYS = listOf(
+ KEY_DESTINATION,
+ KEY_HIGHLIGHT_ENTRY,
+ KEY_SESSION_SOURCE_NAME
+)
+
+private fun createBaseIntent(): Intent? {
+ val context = SpaEnvironmentFactory.instance.appContext
+ val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
+ return Intent().setComponent(ComponentName(context, browseActivityClass))
+}
+
+fun SettingsPage.createIntent(sessionName: String? = null): Intent? {
+ if (!isBrowsable()) return null
+ return createBaseIntent()?.appendSpaParams(
+ destination = buildRoute(),
+ sessionName = sessionName
+ )
+}
+
+fun SettingsEntry.createIntent(sessionName: String? = null): Intent? {
+ val sp = containerPage()
+ if (!sp.isBrowsable()) return null
+ return createBaseIntent()?.appendSpaParams(
+ destination = sp.buildRoute(),
+ entryId = id,
+ sessionName = sessionName
+ )
+}
+
+fun Intent.appendSpaParams(
+ destination: String? = null,
+ entryId: String? = null,
+ sessionName: String? = null
+): Intent {
+ return apply {
+ if (destination != null) putExtra(KEY_DESTINATION, destination)
+ if (entryId != null) putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
+ if (sessionName != null) putExtra(KEY_SESSION_SOURCE_NAME, sessionName)
+ }
+}
+
+fun Intent.getDestination(): String? {
+ return getStringExtra(KEY_DESTINATION)
+}
+
+fun Intent.getEntryId(): String? {
+ return getStringExtra(KEY_HIGHLIGHT_ENTRY)
+}
+
+fun Intent.getSessionName(): String? {
+ return getStringExtra(KEY_SESSION_SOURCE_NAME)
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
similarity index 91%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 3689e4e..02aed1c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework
+package com.android.settingslib.spa.search
import android.content.ContentProvider
import android.content.ContentValues
@@ -26,12 +26,15 @@
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
+import androidx.annotation.VisibleForTesting
import com.android.settingslib.spa.framework.common.ColumnEnum
import com.android.settingslib.spa.framework.common.QueryEnum
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
import com.android.settingslib.spa.framework.common.addUri
import com.android.settingslib.spa.framework.common.getColumns
+import com.android.settingslib.spa.framework.util.SESSION_SEARCH
+import com.android.settingslib.spa.framework.util.createIntent
private const val TAG = "SpaSearchProvider"
@@ -115,7 +118,8 @@
}
}
- private fun querySearchImmutableStatusData(): Cursor {
+ @VisibleForTesting
+ fun querySearchImmutableStatusData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -125,7 +129,8 @@
return cursor
}
- private fun querySearchMutableStatusData(): Cursor {
+ @VisibleForTesting
+ fun querySearchMutableStatusData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -135,7 +140,8 @@
return cursor
}
- private fun querySearchStaticData(): Cursor {
+ @VisibleForTesting
+ fun querySearchStaticData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_STATIC_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -145,7 +151,8 @@
return cursor
}
- private fun querySearchDynamicData(): Cursor {
+ @VisibleForTesting
+ fun querySearchDynamicData(): Cursor {
val entryRepository by spaEnvironment.entryRepository
val cursor = MatrixCursor(QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.getColumns())
for (entry in entryRepository.getAllEntries()) {
@@ -157,20 +164,19 @@
private fun fetchSearchData(entry: SettingsEntry, cursor: MatrixCursor) {
val entryRepository by spaEnvironment.entryRepository
- val browseActivityClass = spaEnvironment.browseActivityClass
// Fetch search data. We can add runtime arguments later if necessary
val searchData = entry.getSearchData() ?: return
- val intent = entry.containerPage()
- .createBrowseIntent(context, browseActivityClass, entry.id)
- ?: Intent()
+ val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
cursor.newRow()
.add(ColumnEnum.ENTRY_ID.id, entry.id)
.add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
.add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
.add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
- .add(ColumnEnum.SEARCH_PATH.id,
- entryRepository.getEntryPathWithTitle(entry.id, searchData.title))
+ .add(
+ ColumnEnum.SEARCH_PATH.id,
+ entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
+ )
}
private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
index 14855a8..7a4750d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SettingsSliceDataRepository.kt
@@ -20,6 +20,7 @@
import android.util.Log
import com.android.settingslib.spa.framework.common.EntrySliceData
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
+import com.android.settingslib.spa.framework.util.getEntryId
private const val TAG = "SliceDataRepository"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
index ff143ed..f362890 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/SliceUtil.kt
@@ -24,9 +24,15 @@
import android.content.Intent
import android.net.Uri
import android.os.Bundle
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
-import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.util.KEY_DESTINATION
+import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.util.SESSION_SLICE
+import com.android.settingslib.spa.framework.util.SPA_INTENT_RESERVED_KEYS
+import com.android.settingslib.spa.framework.util.appendSpaParams
+import com.android.settingslib.spa.framework.util.getDestination
+import com.android.settingslib.spa.framework.util.getEntryId
// Defines SliceUri, which contains special query parameters:
// -- KEY_DESTINATION: The route that this slice is navigated to.
@@ -35,11 +41,6 @@
// Use {entryId, runtimeParams} as the unique Id of this Slice.
typealias SliceUri = Uri
-val RESERVED_KEYS = listOf(
- KEY_DESTINATION,
- KEY_HIGHLIGHT_ENTRY
-)
-
fun SliceUri.getEntryId(): String? {
return getQueryParameter(KEY_HIGHLIGHT_ENTRY)
}
@@ -51,7 +52,7 @@
fun SliceUri.getRuntimeArguments(): Bundle {
val params = Bundle()
for (queryName in queryParameterNames) {
- if (RESERVED_KEYS.contains(queryName)) continue
+ if (SPA_INTENT_RESERVED_KEYS.contains(queryName)) continue
params.putString(queryName, getQueryParameter(queryName))
}
return params
@@ -63,12 +64,12 @@
return "${entryId}_$params"
}
-fun Uri.Builder.appendSliceParams(
- route: String? = null,
+fun Uri.Builder.appendSpaParams(
+ destination: String? = null,
entryId: String? = null,
runtimeArguments: Bundle? = null
): Uri.Builder {
- if (route != null) appendQueryParameter(KEY_DESTINATION, route)
+ if (destination != null) appendQueryParameter(KEY_DESTINATION, destination)
if (entryId != null) appendQueryParameter(KEY_HIGHLIGHT_ENTRY, entryId)
if (runtimeArguments != null) {
for (key in runtimeArguments.keySet()) {
@@ -78,6 +79,20 @@
return this
}
+fun Uri.Builder.fromEntry(
+ entry: SettingsEntry,
+ authority: String?,
+ runtimeArguments: Bundle? = null
+): Uri.Builder {
+ if (authority == null) return this
+ val sp = entry.containerPage()
+ return scheme("content").authority(authority).appendSpaParams(
+ destination = sp.buildRoute(),
+ entryId = entry.id,
+ runtimeArguments = runtimeArguments
+ )
+}
+
fun SliceUri.createBroadcastPendingIntent(): PendingIntent? {
val context = SpaEnvironmentFactory.instance.appContext
val sliceBroadcastClass =
@@ -97,8 +112,8 @@
fun Intent.createBrowsePendingIntent(): PendingIntent? {
val context = SpaEnvironmentFactory.instance.appContext
val browseActivityClass = SpaEnvironmentFactory.instance.browseActivityClass ?: return null
- val destination = getStringExtra(KEY_DESTINATION) ?: return null
- val entryId = getStringExtra(KEY_HIGHLIGHT_ENTRY)
+ val destination = getDestination() ?: return null
+ val entryId = getEntryId()
return createBrowsePendingIntent(context, browseActivityClass, destination, entryId)
}
@@ -109,15 +124,12 @@
entryId: String?
): PendingIntent {
val intent = Intent().setComponent(ComponentName(context, browseActivityClass))
+ .appendSpaParams(destination, entryId, SESSION_SLICE)
.apply {
// Set both extra and data (which is a Uri) in Slice Intent:
// 1) extra is used in SPA navigation framework
// 2) data is used in Slice framework
- putExtra(KEY_DESTINATION, destination)
- if (entryId != null) {
- putExtra(KEY_HIGHLIGHT_ENTRY, entryId)
- }
- data = Uri.Builder().appendSliceParams(destination, entryId).build()
+ data = Uri.Builder().appendSpaParams(destination, entryId).build()
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
@@ -130,7 +142,7 @@
entryId: String
): PendingIntent {
val intent = Intent().setComponent(ComponentName(context, sliceBroadcastClass))
- .apply { data = Uri.Builder().appendSliceParams(entryId = entryId).build() }
+ .apply { data = Uri.Builder().appendSpaParams(entryId = entryId).build() }
return PendingIntent.getBroadcast(
context, 0 /* requestCode */, intent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
index 77c564b..b6099e9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/Preference.kt
@@ -24,7 +24,6 @@
import androidx.compose.ui.graphics.vector.ImageVector
import com.android.settingslib.spa.framework.common.EntryMacro
import com.android.settingslib.spa.framework.common.EntrySearchData
-import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.util.EntryHighlight
@@ -56,10 +55,6 @@
keyword = searchKeywords
)
}
-
- override fun getStatusData(): EntryStatusData {
- return EntryStatusData(isDisabled = false)
- }
}
/**
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
index bde3bba..bd5884d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/BrowseActivityTest.kt
@@ -30,6 +30,7 @@
import com.android.settingslib.spa.framework.common.createSettingsPage
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth
import org.junit.Rule
@@ -45,7 +46,8 @@
private val context: Context = ApplicationProvider.getApplicationContext()
private val spaLogger = SpaLoggerForTest()
- private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()), logger = spaLogger)
@Test
fun testBrowsePage() {
@@ -58,13 +60,7 @@
val sppLayer1 = sppRepository.getProviderOrNull("SppLayer1")!!
val pageLayer1 = sppLayer1.createSettingsPage()
- composeTestRule.setContent {
- BrowseContent(
- allProviders = listOf(sppHome, sppLayer1),
- initialDestination = pageHome.buildRoute(),
- initialEntryId = null
- )
- }
+ composeTestRule.setContent { BrowseContent(sppRepository) }
composeTestRule.onNodeWithText(sppHome.getTitle(null)).assertIsDisplayed()
spaLogger.verifyPageEvent(pageHome.id, 1, 0)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
index f8339b6..c0b7464 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -23,6 +23,8 @@
import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer1
import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,7 +32,8 @@
@RunWith(AndroidJUnit4::class)
class SettingsEntryRepositoryTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val entryRepository by spaEnvironment.entryRepository
@Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index 2017d53..f98963c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -20,6 +20,8 @@
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.core.os.bundleOf
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -64,6 +66,7 @@
assertThat(entry.isAllowSearch).isFalse()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isFalse()
+ assertThat(entry.hasSliceSupport).isFalse()
}
@Test
@@ -121,12 +124,13 @@
@Test
fun testSetAttributes() {
val owner = SettingsPage.create("mySpp")
- val entry = SettingsEntryBuilder.create(owner, "myEntry")
+ val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
.setDisplayName("myEntryDisplay")
- .setIsAllowSearch(true)
.setIsSearchDataDynamic(false)
.setHasMutableStatus(true)
- .build()
+ .setSearchDataFn { null }
+ .setSliceDataFn { _, _ -> null }
+ val entry = entryBuilder.build()
assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
assertThat(entry.displayName).isEqualTo("myEntryDisplay")
assertThat(entry.fromPage).isNull()
@@ -134,6 +138,10 @@
assertThat(entry.isAllowSearch).isTrue()
assertThat(entry.isSearchDataDynamic).isFalse()
assertThat(entry.hasMutableStatus).isTrue()
+ assertThat(entry.hasSliceSupport).isTrue()
+
+ val entry2 = entryBuilder.clearSearchDataFn().build()
+ assertThat(entry2.isAllowSearch).isFalse()
}
@Test
@@ -150,6 +158,10 @@
val rtArguments = bundleOf("rtParam" to "v2")
composeTestRule.setContent { entry.UiLayout(rtArguments) }
+ assertThat(entry.isAllowSearch).isTrue()
+ assertThat(entry.isSearchDataDynamic).isFalse()
+ assertThat(entry.hasMutableStatus).isFalse()
+ assertThat(entry.hasSliceSupport).isFalse()
val searchData = entry.getSearchData(rtArguments)
val statusData = entry.getStatusData(rtArguments)
assertThat(searchData?.title).isEqualTo("myTitle")
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
index 743b5e3..1f5de2d 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -22,9 +22,8 @@
import androidx.navigation.navArgument
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.testutils.BlankActivity
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
-import com.android.settingslib.spa.tests.testutils.SpaLoggerForTest
+import com.android.settingslib.spa.tests.testutils.getUniquePageId
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -32,8 +31,7 @@
@RunWith(AndroidJUnit4::class)
class SettingsPageTest {
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaLogger = SpaLoggerForTest()
- private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+ private val spaEnvironment = SpaEnvironmentForTest(context)
@Test
fun testNullPage() {
@@ -45,9 +43,7 @@
assertThat(page.isCreateBy("NULL")).isTrue()
assertThat(page.isCreateBy("Spp")).isFalse()
assertThat(page.hasRuntimeParam()).isFalse()
- assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
- assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
- assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
+ assertThat(page.isBrowsable()).isFalse()
}
@Test
@@ -60,11 +56,7 @@
assertThat(page.isCreateBy("NULL")).isFalse()
assertThat(page.isCreateBy("mySpp")).isTrue()
assertThat(page.hasRuntimeParam()).isFalse()
- assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
- assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
- assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
- "-e spaActivityDestination mySpp"
- )
+ assertThat(page.isBrowsable()).isTrue()
}
@Test
@@ -74,20 +66,20 @@
"int_param" to 10,
)
val page = spaEnvironment.createPage("SppWithParam", arguments)
- assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
- navArgument("string_param") { type = NavType.StringType },
- navArgument("int_param") { type = NavType.IntType },
- ), arguments))
+ assertThat(page.id).isEqualTo(
+ getUniquePageId(
+ "SppWithParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ ), arguments
+ )
+ )
assertThat(page.sppName).isEqualTo("SppWithParam")
assertThat(page.displayName).isEqualTo("SppWithParam")
assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
assertThat(page.isCreateBy("SppWithParam")).isTrue()
assertThat(page.hasRuntimeParam()).isFalse()
- assertThat(page.isBrowsable(context, BlankActivity::class.java)).isTrue()
- assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNotNull()
- assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).contains(
- "-e spaActivityDestination SppWithParam/myStr/10"
- )
+ assertThat(page.isBrowsable()).isTrue()
}
@Test
@@ -98,33 +90,20 @@
"rt_param" to "rtStr",
)
val page = spaEnvironment.createPage("SppWithRtParam", arguments)
- assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
- navArgument("string_param") { type = NavType.StringType },
- navArgument("int_param") { type = NavType.IntType },
- navArgument("rt_param") { type = NavType.StringType },
- ), arguments))
+ assertThat(page.id).isEqualTo(
+ getUniquePageId(
+ "SppWithRtParam", listOf(
+ navArgument("string_param") { type = NavType.StringType },
+ navArgument("int_param") { type = NavType.IntType },
+ navArgument("rt_param") { type = NavType.StringType },
+ ), arguments
+ )
+ )
assertThat(page.sppName).isEqualTo("SppWithRtParam")
assertThat(page.displayName).isEqualTo("SppWithRtParam")
assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
assertThat(page.hasRuntimeParam()).isTrue()
- assertThat(page.isBrowsable(context, BlankActivity::class.java)).isFalse()
- assertThat(page.createBrowseIntent(context, BlankActivity::class.java)).isNull()
- assertThat(page.createBrowseAdbCommand(context, BlankActivity::class.java)).isNull()
- }
-
- @Test
- fun testPageEvent() {
- spaLogger.reset()
- SpaEnvironmentFactory.reset(spaEnvironment)
- val page = spaEnvironment.createPage("SppHome")
- page.enterPage()
- page.leavePage()
- page.enterPage()
- assertThat(page.createBrowseIntent()).isNotNull()
- assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
- .isEqualTo(2)
- assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
- .isEqualTo(1)
+ assertThat(page.isBrowsable()).isFalse()
}
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
new file mode 100644
index 0000000..1854728
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SpaIntentTest.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaIntentTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment = SpaEnvironmentForTest(context)
+
+ @Before
+ fun setEnvironment() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ }
+
+ @Test
+ fun testCreateIntent() {
+ val nullPage = SettingsPage.createNull()
+ Truth.assertThat(nullPage.createIntent()).isNull()
+ Truth.assertThat(SettingsEntryBuilder.createInject(nullPage).build().createIntent())
+ .isNull()
+
+ val page = spaEnvironment.createPage("SppHome")
+ val pageIntent = page.createIntent()
+ Truth.assertThat(pageIntent).isNotNull()
+ Truth.assertThat(pageIntent!!.getDestination()).isEqualTo(page.buildRoute())
+ Truth.assertThat(pageIntent.getEntryId()).isNull()
+ Truth.assertThat(pageIntent.getSessionName()).isNull()
+
+ val entry = SettingsEntryBuilder.createInject(page).build()
+ val entryIntent = entry.createIntent(SESSION_SEARCH)
+ Truth.assertThat(entryIntent).isNotNull()
+ Truth.assertThat(entryIntent!!.getDestination()).isEqualTo(page.buildRoute())
+ Truth.assertThat(entryIntent.getEntryId()).isEqualTo(entry.id)
+ Truth.assertThat(entryIntent.getSessionName()).isEqualTo(SESSION_SEARCH)
+ }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
new file mode 100644
index 0000000..cdb0f3a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.search
+
+import android.content.Context
+import android.database.Cursor
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.common.ColumnEnum
+import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.common.getIndex
+import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppForSearch
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SpaSearchProviderTest {
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
+ private val searchProvider = SpaSearchProvider()
+
+ @Test
+ fun testQuerySearchStatusData() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val pageOwner = spaEnvironment.createPage("SppForSearch")
+
+ val immutableStatus = searchProvider.querySearchImmutableStatusData()
+ Truth.assertThat(immutableStatus.count).isEqualTo(1)
+ immutableStatus.moveToFirst()
+ immutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+ )
+
+ val mutableStatus = searchProvider.querySearchMutableStatusData()
+ Truth.assertThat(mutableStatus.count).isEqualTo(2)
+ mutableStatus.moveToFirst()
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchStaticWithMutableStatus")
+ )
+
+ mutableStatus.moveToNext()
+ mutableStatus.checkValue(
+ QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+ ColumnEnum.ENTRY_ID,
+ pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+ )
+ }
+
+ @Test
+ fun testQuerySearchIndexData() {
+ SpaEnvironmentFactory.reset(spaEnvironment)
+ val staticData = searchProvider.querySearchStaticData()
+ Truth.assertThat(staticData.count).isEqualTo(2)
+
+ val dynamicData = searchProvider.querySearchDynamicData()
+ Truth.assertThat(dynamicData.count).isEqualTo(2)
+ }
+}
+
+private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
+ Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+}
+
+private fun SettingsPage.getEntryId(name: String): String {
+ return SettingsEntryBuilder.create(this, name).build().id
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
index 6ebd64f..1bdba29 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SettingsSliceDataRepositoryTest.kt
@@ -18,15 +18,16 @@
import android.content.Context
import android.net.Uri
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import androidx.slice.Slice
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.common.getUniqueEntryId
-import com.android.settingslib.spa.testutils.InstantTaskExecutorRule
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
+import com.android.settingslib.spa.tests.testutils.SppHome
import com.android.settingslib.spa.tests.testutils.SppLayer2
+import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
import org.junit.Test
@@ -37,7 +38,8 @@
@get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
private val context: Context = ApplicationProvider.getApplicationContext()
- private val spaEnvironment = SpaEnvironmentForTest(context)
+ private val spaEnvironment =
+ SpaEnvironmentForTest(context, listOf(SppHome.createSettingsPage()))
private val sliceDataRepository by spaEnvironment.sliceDataRepository
@Test
@@ -48,7 +50,7 @@
// Slice supported
val page = SppLayer2.createSettingsPage()
val entryId = getUniqueEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+ val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
assertThat(sliceUri.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
@@ -57,7 +59,7 @@
// Slice unsupported
val entryId2 = getUniqueEntryId("Layer2Entry2", page)
- val sliceUri2 = Uri.Builder().appendSliceParams(page.buildRoute(), entryId2).build()
+ val sliceUri2 = Uri.Builder().appendSpaParams(page.buildRoute(), entryId2).build()
assertThat(sliceUri2.getDestination()).isEqualTo("SppLayer2")
assertThat(sliceUri2.getSliceId()).isEqualTo("${entryId2}_Bundle[{}]")
assertThat(sliceDataRepository.getOrBuildSliceData(sliceUri2)).isNull()
@@ -67,7 +69,7 @@
fun getActiveSliceDataTest() {
val page = SppLayer2.createSettingsPage()
val entryId = getUniqueEntryId("Layer2Entry1", page)
- val sliceUri = Uri.Builder().appendSliceParams(page.buildRoute(), entryId).build()
+ val sliceUri = Uri.Builder().appendSpaParams(page.buildRoute(), entryId).build()
// build slice data first
val sliceData = sliceDataRepository.getOrBuildSliceData(sliceUri)
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
index 16a87f6..d1c4e51 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/slice/SliceUtilTest.kt
@@ -43,14 +43,14 @@
// valid slice uri
val dest = "myRoute"
val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+ val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
assertThat(sliceUriWithoutParams.getEntryId()).isEqualTo(entryId)
assertThat(sliceUriWithoutParams.getDestination()).isEqualTo(dest)
assertThat(sliceUriWithoutParams.getRuntimeArguments().size()).isEqualTo(0)
assertThat(sliceUriWithoutParams.getSliceId()).isEqualTo("${entryId}_Bundle[{}]")
val sliceUriWithParams =
- Uri.Builder().appendSliceParams(dest, entryId, bundleOf("p1" to "v1")).build()
+ Uri.Builder().appendSpaParams(dest, entryId, bundleOf("p1" to "v1")).build()
assertThat(sliceUriWithParams.getEntryId()).isEqualTo(entryId)
assertThat(sliceUriWithParams.getDestination()).isEqualTo(dest)
assertThat(sliceUriWithParams.getRuntimeArguments().size()).isEqualTo(1)
@@ -67,7 +67,7 @@
// Valid Slice Uri
val dest = "myRoute"
val entryId = "myEntry"
- val sliceUriWithoutParams = Uri.Builder().appendSliceParams(dest, entryId).build()
+ val sliceUriWithoutParams = Uri.Builder().appendSpaParams(dest, entryId).build()
val pendingIntent = sliceUriWithoutParams.createBroadcastPendingIntent()
assertThat(pendingIntent).isNotNull()
assertThat(pendingIntent!!.isBroadcast).isTrue()
@@ -87,7 +87,7 @@
// Valid Slice Uri
val dest = "myRoute"
val entryId = "myEntry"
- val sliceUri = Uri.Builder().appendSliceParams(dest, entryId).build()
+ val sliceUri = Uri.Builder().appendSpaParams(dest, entryId).build()
val pendingIntent = sliceUri.createBrowsePendingIntent()
assertThat(pendingIntent).isNotNull()
assertThat(pendingIntent!!.isActivity).isTrue()
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
index ab269f2..f38bd08 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/SpaEnvironmentForTest.kt
@@ -24,7 +24,9 @@
import androidx.navigation.NavType
import androidx.navigation.navArgument
import com.android.settingslib.spa.framework.BrowseActivity
+import com.android.settingslib.spa.framework.common.EntrySearchData
import com.android.settingslib.spa.framework.common.EntrySliceData
+import com.android.settingslib.spa.framework.common.EntryStatusData
import com.android.settingslib.spa.framework.common.LogCategory
import com.android.settingslib.spa.framework.common.LogEvent
import com.android.settingslib.spa.framework.common.SettingsEntry
@@ -49,7 +51,7 @@
messageCount[key] = (messageCount[key] ?: 0) + 1
}
- override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+ override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
val key = EventCountKey(id, event, category)
eventCount[key] = (eventCount[key] ?: 0) + 1
}
@@ -141,8 +143,43 @@
}
}
+object SppForSearch : SettingsPageProvider {
+ override val name = "SppForSearch"
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val owner = this.createSettingsPage()
+ return listOf(
+ SettingsEntryBuilder.create(owner, "SearchStaticWithNoStatus")
+ .setSearchDataFn { EntrySearchData(title = "SearchStaticWithNoStatus") }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchStaticWithMutableStatus")
+ .setHasMutableStatus(true)
+ .setSearchDataFn { EntrySearchData(title = "SearchStaticWithMutableStatus") }
+ .setStatusDataFn { EntryStatusData(isSwitchOff = true) }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchDynamicWithMutableStatus")
+ .setIsSearchDataDynamic(true)
+ .setHasMutableStatus(true)
+ .setSearchDataFn { EntrySearchData(title = "SearchDynamicWithMutableStatus") }
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
+ .build(),
+ SettingsEntryBuilder.create(owner, "SearchDynamicWithImmutableStatus")
+ .setIsSearchDataDynamic(true)
+ .setSearchDataFn {
+ EntrySearchData(
+ title = "SearchDynamicWithImmutableStatus",
+ keyword = listOf("kw1", "kw2")
+ )
+ }
+ .setStatusDataFn { EntryStatusData(isDisabled = true) }
+ .build(),
+ )
+ }
+}
+
class SpaEnvironmentForTest(
context: Context,
+ rootPages: List<SettingsPage> = emptyList(),
override val browseActivityClass: Class<out Activity>? = BlankActivity::class.java,
override val sliceBroadcastReceiverClass: Class<out BroadcastReceiver>? =
BlankSliceBroadcastReceiver::class.java,
@@ -153,6 +190,7 @@
SettingsPageProviderRepository(
listOf(
SppHome, SppLayer1, SppLayer2,
+ SppForSearch,
object : SettingsPageProvider {
override val name = "SppWithParam"
override val parameter = listOf(
@@ -169,7 +207,7 @@
)
},
),
- listOf(SettingsPage.create("SppHome"))
+ rootPages
)
}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 93f9afe..7e51fea 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.tests.testutils
import android.os.Bundle
import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.toHashId
import com.android.settingslib.spa.framework.util.normalize
fun getUniquePageId(
diff --git a/packages/SettingsLib/Spa/testutils/Android.bp b/packages/SettingsLib/Spa/testutils/Android.bp
index 48df569..de87dde 100644
--- a/packages/SettingsLib/Spa/testutils/Android.bp
+++ b/packages/SettingsLib/Spa/testutils/Android.bp
@@ -24,7 +24,7 @@
srcs: ["src/**/*.kt"],
static_libs: [
- "androidx.arch.core_core-runtime",
+ "androidx.arch.core_core-testing",
"androidx.compose.ui_ui-test-junit4",
"androidx.compose.ui_ui-test-manifest",
"mockito",
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index be8df43..81e54c1 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -47,7 +47,7 @@
}
dependencies {
- api "androidx.arch.core:core-runtime:2.1.0"
+ api "androidx.arch.core:core-testing:2.1.0"
api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
api "com.google.truth:truth:1.1.3"
api "org.mockito:mockito-core:2.21.0"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
deleted file mode 100644
index 43c18d4..0000000
--- a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/InstantTaskExecutorRule.kt
+++ /dev/null
@@ -1,55 +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.settingslib.spa.testutils
-
-import androidx.arch.core.executor.ArchTaskExecutor
-import androidx.arch.core.executor.TaskExecutor
-import org.junit.rules.TestWatcher
-import org.junit.runner.Description
-
-/**
- * Test rule that makes ArchTaskExecutor main thread assertions pass. There is one such assert
- * in LifecycleRegistry.
-
- * This is a copy of androidx/arch/core/executor/testing/InstantTaskExecutorRule which should be
- * replaced once the dependency issue is solved.
- */
-class InstantTaskExecutorRule : TestWatcher() {
- override fun starting(description: Description) {
- super.starting(description)
- ArchTaskExecutor.getInstance().setDelegate(
- object : TaskExecutor() {
- override fun executeOnDiskIO(runnable: Runnable) {
- runnable.run()
- }
-
- override fun postToMainThread(runnable: Runnable) {
- runnable.run()
- }
-
- override fun isMainThread(): Boolean {
- return true
- }
- }
- )
- }
-
- override fun finished(description: Description) {
- super.finished(description)
- ArchTaskExecutor.getInstance().setDelegate(null)
- }
-}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
index a7122d0..6999908 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListModel.kt
@@ -33,7 +33,8 @@
*
* @return the [AppRecord] list which will be displayed.
*/
- fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>>
+ fun filter(userIdFlow: Flow<Int>, option: Int, recordListFlow: Flow<List<T>>): Flow<List<T>> =
+ recordListFlow
/**
* This function is called when the App List's loading is finished and displayed to the user.
@@ -67,5 +68,5 @@
* @return null if no summary should be displayed.
*/
@Composable
- fun getSummary(option: Int, record: T): State<String>?
+ fun getSummary(option: Int, record: T): State<String>? = null
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 681eb1c..15766e1 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -55,19 +55,22 @@
val searchQuery: State<String>,
)
+internal data class AppListInput<T : AppRecord>(
+ val config: AppListConfig,
+ val listModel: AppListModel<T>,
+ val state: AppListState,
+ val header: @Composable () -> Unit,
+ val appItem: @Composable AppListItemModel<T>.() -> Unit,
+ val bottomPadding: Dp,
+)
+
/**
* The template to render an App List.
*
* This UI element will take the remaining space on the screen to show the App List.
*/
@Composable
-internal fun <T : AppRecord> AppList(
- config: AppListConfig,
- listModel: AppListModel<T>,
- state: AppListState,
- header: @Composable () -> Unit,
- appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
- bottomPadding: Dp,
+internal fun <T : AppRecord> AppListInput<T>.AppList(
appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
loadAppListData(config, listModel, state)
},
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
index ac3f8ff..28bf832 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListItem.kt
@@ -35,16 +35,13 @@
)
@Composable
-fun <T : AppRecord> AppListItem(
- itemModel: AppListItemModel<T>,
- onClick: () -> Unit,
-) {
+fun <T : AppRecord> AppListItemModel<T>.AppListItem(onClick: () -> Unit) {
Preference(remember {
object : PreferenceModel {
- override val title = itemModel.label
- override val summary = itemModel.summary
+ override val title = label
+ override val summary = this@AppListItem.summary
override val icon = @Composable {
- AppIcon(app = itemModel.record.app, size = SettingsDimension.appIconItemSize)
+ AppIcon(app = record.app, size = SettingsDimension.appIconItemSize)
}
override val onClick = onClick
}
@@ -58,7 +55,6 @@
val record = object : AppRecord {
override val app = LocalContext.current.applicationInfo
}
- val itemModel = AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState())
- AppListItem(itemModel) {}
+ AppListItemModel<AppRecord>(record, "Chrome", "Allowed".toState()).AppListItem {}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index f371ce9..d452c74 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -47,7 +47,23 @@
primaryUserOnly: Boolean = false,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
header: @Composable () -> Unit = {},
- appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+ appItem: @Composable AppListItemModel<T>.() -> Unit,
+) {
+ AppListPageImpl(
+ title, listModel, showInstantApps, primaryUserOnly, moreOptions, header, appItem,
+ ) { it.AppList() }
+}
+
+@Composable
+internal fun <T : AppRecord> AppListPageImpl(
+ title: String,
+ listModel: AppListModel<T>,
+ showInstantApps: Boolean = false,
+ primaryUserOnly: Boolean = false,
+ moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
+ header: @Composable () -> Unit = {},
+ appItem: @Composable AppListItemModel<T>.() -> Unit,
+ appList: @Composable (input: AppListInput<T>) -> Unit,
) {
val showSystem = rememberSaveable { mutableStateOf(false) }
SearchScaffold(
@@ -64,7 +80,7 @@
val options = remember { listModel.getSpinnerOptions() }
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
- AppList(
+ val appListInput = AppListInput(
config = AppListConfig(
userId = userInfo.id,
showInstantApps = showInstantApps,
@@ -79,6 +95,7 @@
appItem = appItem,
bottomPadding = bottomPadding,
)
+ appList(appListInput)
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
index 5290bec..452971b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListSwitchItem.kt
@@ -9,8 +9,7 @@
import com.android.settingslib.spaprivileged.model.app.AppRecord
@Composable
-fun <T : AppRecord> AppListSwitchItem(
- itemModel: AppListItemModel<T>,
+fun <T : AppRecord> AppListItemModel<T>.AppListSwitchItem(
onClick: () -> Unit,
checked: State<Boolean?>,
changeable: State<Boolean>,
@@ -19,14 +18,14 @@
TwoTargetSwitchPreference(
model = remember {
object : SwitchPreferenceModel {
- override val title = itemModel.label
- override val summary = itemModel.summary
+ override val title = label
+ override val summary = this@AppListSwitchItem.summary
override val checked = checked
override val changeable = changeable
override val onCheckedChange = onCheckedChange
}
},
- icon = { AppIcon(itemModel.record.app, SettingsDimension.appIconItemSize) },
+ icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
onClick = onClick,
)
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index de5a4a2..8287693 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -66,7 +66,7 @@
val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
val entryList = mutableListOf<SettingsEntry>()
entryList.add(
- SettingsEntryBuilder.create(ENTRY_NAME, owner).setIsAllowSearch(false).build()
+ SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
)
return entryList
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
index 6db2733..00eb607 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt
@@ -70,7 +70,6 @@
entryList.add(
SettingsEntryBuilder.createLinkFrom("${ENTRY_NAME}_$category", appListPage)
.setLink(toPage = appInfoPage)
- .setIsAllowSearch(false)
.build()
)
}
@@ -92,12 +91,11 @@
AppListPage(
title = stringResource(listModel.pageTitleResId),
listModel = internalListModel,
- ) { itemModel ->
+ ) {
AppListItem(
- itemModel = itemModel,
onClick = TogglePermissionAppInfoPageProvider.navigator(
permissionType = permissionType,
- app = itemModel.record.app,
+ app = record.app,
),
)
}
@@ -120,7 +118,7 @@
parameter = PAGE_PARAMETER,
arguments = bundleOf(PERMISSION to permissionType)
)
- return SettingsEntryBuilder.createInject(owner = appListPage).setIsAllowSearch(false)
+ return SettingsEntryBuilder.createInject(owner = appListPage)
.setUiLayoutFn {
val listModel = rememberContext(listModelSupplier)
Preference(
diff --git a/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
new file mode 100644
index 0000000..fb1e09a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/res/values/strings.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <!-- Test Permission title. [DO NOT TRANSLATE] -->
+ <string name="test_permission_title" translatable="false">Test Permission</string>
+
+ <!-- Test Permission switch title. [DO NOT TRANSLATE] -->
+ <string name="test_permission_switch_title" translatable="false">Allow Test Permission</string>
+
+ <!-- Test Permission footer. [DO NOT TRANSLATE] -->
+ <string name="test_permission_footer" translatable="false">Test Permission is for demo.</string>
+</resources>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
index bc6925b..c4f2df2 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -40,9 +40,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppListRepositoryTest {
-
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index b570815..b9c875b 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -21,7 +21,7 @@
import androidx.compose.runtime.Composable
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
-import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spa.framework.util.mapItem
import com.android.settingslib.spa.testutils.waitUntil
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -39,8 +39,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class AppListViewModelTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
@@ -117,16 +116,7 @@
var onFirstLoadedCalled = false
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
+ appListFlow.mapItem(::TestAppRecord)
override suspend fun onFirstLoaded(recordList: List<TestAppRecord>) {
onFirstLoadedCalled = true
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
index 4207490..4002655 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -40,8 +40,7 @@
@RunWith(AndroidJUnit4::class)
class PackageManagerExtTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@Mock
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
new file mode 100644
index 0000000..c3c96c6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun title_isDisplayed() {
+ setContent()
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun appListState_hasCorrectInitialState() {
+ val inputState by setContent()
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isFalse()
+ assertThat(state.option.value).isEqualTo(0)
+ assertThat(state.searchQuery.value).isEqualTo("")
+ }
+
+ @Test
+ fun canShowSystem() {
+ val inputState by setContent()
+
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isTrue()
+ }
+
+ @Test
+ fun afterShowSystem_displayHideSystem() {
+ setContent()
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
+
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ ).performClick()
+
+ composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun whenHasOptions_firstOptionDisplayed() {
+ val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
+ val state = inputState!!.state
+ assertThat(state.option.value).isEqualTo(0)
+ }
+
+ @Test
+ fun whenHasOptions_couldSwitchOption() {
+ val inputState by setContent(options = listOf(OPTION_0, OPTION_1))
+
+ composeTestRule.onNodeWithText(OPTION_0).performClick()
+ composeTestRule.onNodeWithText(OPTION_1).performClick()
+
+ composeTestRule.onNodeWithText(OPTION_1).assertIsDisplayed()
+ composeTestRule.onNodeWithText(OPTION_0).assertDoesNotExist()
+ val state = inputState!!.state
+ assertThat(state.option.value).isEqualTo(1)
+ }
+
+ private fun setContent(
+ options: List<String> = emptyList(),
+ header: @Composable () -> Unit = {},
+ ): State<AppListInput<TestAppRecord>?> {
+ val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
+ composeTestRule.setContent {
+ AppListPageImpl(
+ title = TITLE,
+ listModel = TestAppListModel(options),
+ header = header,
+ appItem = { AppListItem {} },
+ appList = { appListState.value = it },
+ )
+ }
+ return appListState
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val OPTION_0 = "Option 1"
+ const val OPTION_1 = "Option 2"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index 9f20c78..df80dd4 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -29,14 +29,12 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spa.framework.compose.stateOf
import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.util.asyncMapItem
import com.android.settingslib.spaprivileged.R
import com.android.settingslib.spaprivileged.model.app.AppEntry
import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
-import com.android.settingslib.spaprivileged.model.app.AppListModel
-import com.android.settingslib.spaprivileged.model.app.AppRecord
-import kotlinx.coroutines.flow.Flow
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppListModel
+import com.android.settingslib.spaprivileged.tests.testutils.TestAppRecord
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -92,21 +90,19 @@
enableGrouping: Boolean = false,
) {
composeTestRule.setContent {
- AppList(
+ val appListInput = AppListInput(
config = AppListConfig(userId = USER_ID, showInstantApps = false),
- listModel = TestAppListModel(enableGrouping),
+ listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
option = 0.toState(),
searchQuery = "".toState(),
),
header = header,
- appItem = { AppListItem(it) {} },
+ appItem = { AppListItem {} },
bottomPadding = 0.dp,
- appListDataSupplier = {
- stateOf(AppListData(appEntries, option = 0))
- }
)
+ appListInput.AppList { stateOf(AppListData(appEntries, option = 0)) }
}
}
@@ -137,25 +133,3 @@
)
}
}
-
-private data class TestAppRecord(
- override val app: ApplicationInfo,
- val group: String? = null,
-) : AppRecord
-
-private class TestAppListModel(val enableGrouping: Boolean) : AppListModel<TestAppRecord> {
- override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- appListFlow.asyncMapItem { TestAppRecord(it) }
-
- @Composable
- override fun getSummary(option: Int, record: TestAppRecord) = null
-
- override fun filter(
- userIdFlow: Flow<Int>,
- option: Int,
- recordListFlow: Flow<List<TestAppRecord>>,
- ) = recordListFlow
-
- override fun getGroupTitle(option: Int, record: TestAppRecord) =
- if (enableGrouping) record.group else null
-}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
index b3638c2..8e98d8c 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppStorageSizeTest.kt
@@ -41,8 +41,7 @@
@RunWith(AndroidJUnit4::class)
class AppStorageSizeTest {
- @JvmField
- @Rule
+ @get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
@get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
new file mode 100644
index 0000000..4bc612a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPageTest.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListPageTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun appListInjectEntry_titleDisplayed() {
+ val entry = TogglePermissionAppListPageProvider.buildInjectEntry(PERMISSION_TYPE) {
+ TestTogglePermissionAppListModel()
+ }.build()
+
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ entry.UiLayout()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun appListRoute() {
+ val route = TogglePermissionAppListPageProvider.getRoute(PERMISSION_TYPE)
+
+ assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ }
+
+ private companion object {
+ const val PERMISSION_TYPE = "test.PERMISSION"
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
new file mode 100644
index 0000000..af3189f
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TogglePermissionAppListTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private var context: Context = ApplicationProvider.getApplicationContext()
+
+ @Test
+ fun appListInjectEntry_titleDisplayed() {
+ val entry = TestTogglePermissionAppListProvider.buildAppListInjectEntry().build()
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ entry.UiLayout()
+ }
+ }
+
+ composeTestRule.onNodeWithText(context.getString(R.string.test_permission_title))
+ .assertIsDisplayed()
+ }
+
+ @Test
+ fun appListRoute() {
+ val route = TestTogglePermissionAppListProvider.getAppListRoute()
+
+ assertThat(route).isEqualTo("TogglePermissionAppList/test.PERMISSION")
+ }
+
+ @Test
+ fun togglePermissionAppListTemplate_createPageProviders() {
+ val togglePermissionAppListTemplate =
+ TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))
+
+ val createPageProviders = togglePermissionAppListTemplate.createPageProviders()
+
+ assertThat(createPageProviders).hasSize(2)
+ assertThat(createPageProviders.any { it is TogglePermissionAppListPageProvider }).isTrue()
+ assertThat(createPageProviders.any { it is TogglePermissionAppInfoPageProvider }).isTrue()
+ }
+}
+
+private object TestTogglePermissionAppListProvider : TogglePermissionAppListProvider {
+ override val permissionType = "test.PERMISSION"
+ override fun createModel(context: Context) = TestTogglePermissionAppListModel()
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
new file mode 100644
index 0000000..ada4016
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestAppListModel.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import com.android.settingslib.spa.framework.util.mapItem
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+
+data class TestAppRecord(
+ override val app: ApplicationInfo,
+ val group: String? = null,
+) : AppRecord
+
+class TestAppListModel(
+ private val options: List<String> = emptyList(),
+ private val enableGrouping: Boolean = false,
+) : AppListModel<TestAppRecord> {
+ override fun getSpinnerOptions() = options
+
+ override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+ appListFlow.mapItem(::TestAppRecord)
+
+ override fun getGroupTitle(option: Int, record: TestAppRecord) =
+ if (enableGrouping) record.group else null
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
new file mode 100644
index 0000000..91a9c6b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/tests/testutils/TestTogglePermissionAppListModel.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.settingslib.spaprivileged.tests.testutils
+
+import android.content.pm.ApplicationInfo
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.template.app.TogglePermissionAppListModel
+import kotlinx.coroutines.flow.Flow
+
+class TestTogglePermissionAppListModel : TogglePermissionAppListModel<TestAppRecord> {
+ override val pageTitleResId = R.string.test_permission_title
+ override val switchTitleResId = R.string.test_permission_switch_title
+ override val footerResId = R.string.test_permission_footer
+
+ override fun transformItem(app: ApplicationInfo) = TestAppRecord(app = app)
+
+ override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<TestAppRecord>>) =
+ recordListFlow
+
+ @Composable
+ override fun isAllowed(record: TestAppRecord) = stateOf(null)
+
+ override fun isChangeable(record: TestAppRecord) = false
+
+ override fun setAllowed(record: TestAppRecord, newAllowed: Boolean) {}
+}
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 3baef4b..e9c6aed 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -23,5 +23,6 @@
apex_available: [
"//apex_available:platform",
"com.android.permission",
+ "com.android.healthconnect",
],
}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7c3948a..87354c7 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -168,25 +168,14 @@
}
android_library {
- name: "SystemUI-tests",
+ name: "SystemUI-tests-base",
manifest: "tests/AndroidManifest-base.xml",
- additional_manifests: ["tests/AndroidManifest.xml"],
-
resource_dirs: [
"tests/res",
"res-product",
"res-keyguard",
"res",
],
- srcs: [
- "tests/src/**/*.kt",
- "tests/src/**/*.java",
- "src/**/*.kt",
- "src/**/*.java",
- "src/**/I*.aidl",
- ":ReleaseJavaFiles",
- ":SystemUI-tests-utils",
- ],
static_libs: [
"WifiTrackerLib",
"SystemUIAnimationLib",
@@ -225,9 +214,6 @@
"metrics-helper-lib",
"hamcrest-library",
"androidx.test.rules",
- "androidx.test.uiautomator_uiautomator",
- "mockito-target-extended-minus-junit4",
- "androidx.test.ext.junit",
"testables",
"truth-prebuilt",
"monet",
@@ -237,6 +223,27 @@
"LowLightDreamLib",
"motion_tool_lib",
],
+}
+
+android_library {
+ name: "SystemUI-tests",
+ manifest: "tests/AndroidManifest-base.xml",
+ additional_manifests: ["tests/AndroidManifest.xml"],
+ srcs: [
+ "tests/src/**/*.kt",
+ "tests/src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.java",
+ "src/**/I*.aidl",
+ ":ReleaseJavaFiles",
+ ":SystemUI-tests-utils",
+ ],
+ static_libs: [
+ "SystemUI-tests-base",
+ "androidx.test.uiautomator_uiautomator",
+ "mockito-target-extended-minus-junit4",
+ "androidx.test.ext.junit",
+ ],
libs: [
"android.test.runner",
"android.test.base",
@@ -253,6 +260,45 @@
},
}
+android_app {
+ name: "SystemUIRobo-stub",
+ defaults: [
+ "platform_app_defaults",
+ "SystemUI_app_defaults",
+ ],
+ manifest: "tests/AndroidManifest-base.xml",
+ static_libs: [
+ "SystemUI-tests-base",
+ ],
+ aaptflags: [
+ "--extra-packages",
+ "com.android.systemui",
+ ],
+ dont_merge_manifests: true,
+ platform_apis: true,
+ system_ext_specific: true,
+ certificate: "platform",
+ privileged: true,
+ resource_dirs: [],
+}
+
+android_robolectric_test {
+ name: "SystemUiRoboTests",
+ srcs: [
+ "tests/robolectric/src/**/*.kt",
+ "tests/robolectric/src/**/*.java",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth-prebuilt",
+ ],
+ kotlincflags: ["-Xjvm-default=enable"],
+ instrumentation_for: "SystemUIRobo-stub",
+ java_resource_dirs: ["tests/robolectric/config"],
+}
+
// Opt-out config for optimizing the SystemUI target using R8.
// Disabled via `export SYSTEMUI_OPTIMIZE_JAVA=false`, or explicitly in Make via
// `SYSTEMUI_OPTIMIZE_JAVA := false`.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index f9c6841..43bfa74 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -320,9 +320,7 @@
counterWallpaper.cleanUp(finishTransaction)
// Release surface references now. This is apparently to free GPU
// memory while doing quick operations (eg. during CTS).
- for (i in info.changes.indices.reversed()) {
- info.changes[i].leash.release()
- }
+ info.releaseAllSurfaces()
for (i in leashMap.size - 1 downTo 0) {
leashMap.valueAt(i).release()
}
@@ -331,6 +329,7 @@
null /* wct */,
finishTransaction
)
+ finishTransaction.close()
} catch (e: RemoteException) {
Log.e(
"ActivityOptionsCompat",
@@ -364,6 +363,9 @@
) {
// TODO: hook up merge to recents onTaskAppeared if applicable. Until then,
// ignore any incoming merges.
+ // Clean up stuff though cuz GC takes too long for benchmark tests.
+ t.close()
+ info.releaseAllSurfaces()
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..f490c54
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+
+class FakeKeyguardQuickAffordanceProviderClient(
+ slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ capacity = 1,
+ ),
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+ capacity = 1,
+ ),
+ ),
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_1,
+ name = AFFORDANCE_1,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_2,
+ name = AFFORDANCE_2,
+ iconResourceId = 0,
+ ),
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = AFFORDANCE_3,
+ name = AFFORDANCE_3,
+ iconResourceId = 0,
+ ),
+ ),
+ flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
+ listOf(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = KeyguardQuickAffordanceProviderContract.FlagsTable.FLAG_NAME_FEATURE_ENABLED,
+ value = true,
+ )
+ ),
+) : KeyguardQuickAffordanceProviderClient {
+
+ private val slots = MutableStateFlow(slots)
+ private val affordances = MutableStateFlow(affordances)
+ private val flags = MutableStateFlow(flags)
+
+ private val selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+ override suspend fun insertSelection(slotId: String, affordanceId: String) {
+ val slotCapacity =
+ querySlots().find { it.id == slotId }?.capacity
+ ?: error("Slot with ID \"$slotId\" not found!")
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ while (affordances.size + 1 > slotCapacity) {
+ affordances.removeAt(0)
+ }
+ affordances.remove(affordanceId)
+ affordances.add(affordanceId)
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return slots.value
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return flags.value
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return slots.asStateFlow()
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return flags.asStateFlow()
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return affordances.value
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return affordances.asStateFlow()
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return toSelectionList(selections.value, affordances.value)
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return combine(selections, affordances) { selections, affordances ->
+ toSelectionList(selections, affordances)
+ }
+ }
+
+ override suspend fun deleteSelection(slotId: String, affordanceId: String) {
+ val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList()
+ affordances.remove(affordanceId)
+
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
+ }
+
+ override suspend fun deleteAllSelections(slotId: String) {
+ selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() }
+ }
+
+ override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable {
+ return BitmapDrawable()
+ }
+
+ fun setFlag(
+ name: String,
+ value: Boolean,
+ ) {
+ flags.value =
+ flags.value.toMutableList().apply {
+ removeIf { it.name == name }
+ add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
+ }
+ }
+
+ fun setSlotCapacity(slotId: String, capacity: Int) {
+ slots.value =
+ slots.value.toMutableList().apply {
+ val index = indexOfFirst { it.id == slotId }
+ check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" }
+ set(
+ index,
+ KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity)
+ )
+ }
+ }
+
+ fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
+ affordances.value = affordances.value + listOf(affordance)
+ return affordances.value.size - 1
+ }
+
+ private fun toSelectionList(
+ selections: Map<String, List<String>>,
+ affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
+ ): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return selections
+ .map { (slotId, affordanceIds) ->
+ affordanceIds.map { affordanceId ->
+ val affordanceName =
+ affordances.find { it.id == affordanceId }?.name
+ ?: error("No affordance with ID of \"$affordanceId\"!")
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = slotId,
+ affordanceId = affordanceId,
+ affordanceName = affordanceName,
+ )
+ }
+ }
+ .flatten()
+ }
+
+ companion object {
+ const val AFFORDANCE_1 = "affordance_1"
+ const val AFFORDANCE_2 = "affordance_2"
+ const val AFFORDANCE_3 = "affordance_3"
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
new file mode 100644
index 0000000..3213b2e
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt
@@ -0,0 +1,479 @@
+/*
+ * 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.shared.quickaffordance.data.content
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import androidx.annotation.DrawableRes
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Client for using a content provider implementing the [Contract]. */
+interface KeyguardQuickAffordanceProviderClient {
+
+ /**
+ * Selects an affordance with the given ID for a slot on the lock screen with the given ID.
+ *
+ * Note that the maximum number of selected affordances on this slot is automatically enforced.
+ * Selecting a slot that is already full (e.g. already has a number of selected affordances at
+ * its maximum capacity) will automatically remove the oldest selected affordance before adding
+ * the one passed in this call. Additionally, selecting an affordance that's already one of the
+ * selected affordances on the slot will move the selected affordance to the newest location in
+ * the slot.
+ */
+ suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Returns all available slots supported by the device. */
+ suspend fun querySlots(): List<Slot>
+
+ /** Returns the list of flags. */
+ suspend fun queryFlags(): List<Flag>
+
+ /**
+ * Returns [Flow] for observing the collection of slots.
+ *
+ * @see [querySlots]
+ */
+ fun observeSlots(): Flow<List<Slot>>
+
+ /**
+ * Returns [Flow] for observing the collection of flags.
+ *
+ * @see [queryFlags]
+ */
+ fun observeFlags(): Flow<List<Flag>>
+
+ /**
+ * Returns all available affordances supported by the device, regardless of current slot
+ * placement.
+ */
+ suspend fun queryAffordances(): List<Affordance>
+
+ /**
+ * Returns [Flow] for observing the collection of affordances.
+ *
+ * @see [queryAffordances]
+ */
+ fun observeAffordances(): Flow<List<Affordance>>
+
+ /** Returns the current slot-affordance selections. */
+ suspend fun querySelections(): List<Selection>
+
+ /**
+ * Returns [Flow] for observing the collection of selections.
+ *
+ * @see [querySelections]
+ */
+ fun observeSelections(): Flow<List<Selection>>
+
+ /** Unselects an affordance with the given ID from the slot with the given ID. */
+ suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ )
+
+ /** Unselects all affordances from the slot with the given ID. */
+ suspend fun deleteAllSelections(
+ slotId: String,
+ )
+
+ /** Returns a [Drawable] with the given ID, loaded from the system UI package. */
+ suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int = Color.WHITE,
+ ): Drawable
+
+ /** Models a slot. A position that quick affordances can be positioned in. */
+ data class Slot(
+ /** Unique ID of the slot. */
+ val id: String,
+ /**
+ * The maximum number of quick affordances that are allowed to be positioned in this slot.
+ */
+ val capacity: Int,
+ )
+
+ /**
+ * Models a quick affordance. An action that can be selected by the user to appear in one or
+ * more slots on the lock screen.
+ */
+ data class Affordance(
+ /** Unique ID of the quick affordance. */
+ val id: String,
+ /** User-facing label for this affordance. */
+ val name: String,
+ /**
+ * Resource ID for the user-facing icon for this affordance. This resource is hosted by the
+ * System UI process so it must be used with
+ * `PackageManager.getResourcesForApplication(String)`.
+ */
+ val iconResourceId: Int,
+ /**
+ * Whether the affordance is enabled. Disabled affordances should be shown on the picker but
+ * should be rendered as "disabled". When tapped, the enablement properties should be used
+ * to populate UI that would explain to the user what to do in order to re-enable this
+ * affordance.
+ */
+ val isEnabled: Boolean = true,
+ /**
+ * If the affordance is disabled, this is a set of instruction messages to be shown to the
+ * user when the disabled affordance is selected. The instructions should help the user
+ * figure out what to do in order to re-neable this affordance.
+ */
+ val enablementInstructions: List<String>? = null,
+ /**
+ * If the affordance is disabled, this is a label for a button shown together with the set
+ * of instruction messages when the disabled affordance is selected. The button should help
+ * send the user to a flow that would help them achieve the instructions and re-enable this
+ * affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionText: String? = null,
+ /**
+ * If the affordance is disabled, this is a "component name" of the format
+ * `packageName/action` to be used as an `Intent` for `startActivity` when the action button
+ * (shown together with the set of instruction messages when the disabled affordance is
+ * selected) is clicked by the user. The button should help send the user to a flow that
+ * would help them achieve the instructions and re-enable this affordance.
+ *
+ * If `null`, the button should not be shown.
+ */
+ val enablementActionComponentName: String? = null,
+ )
+
+ /** Models a selection of a quick affordance on a slot. */
+ data class Selection(
+ /** The unique ID of the slot. */
+ val slotId: String,
+ /** The unique ID of the quick affordance. */
+ val affordanceId: String,
+ /** The user-visible label for the quick affordance. */
+ val affordanceName: String,
+ )
+
+ /** Models a System UI flag. */
+ data class Flag(
+ /** The name of the flag. */
+ val name: String,
+ /** The value of the flag. */
+ val value: Boolean,
+ )
+}
+
+class KeyguardQuickAffordanceProviderClientImpl(
+ private val context: Context,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : KeyguardQuickAffordanceProviderClient {
+
+ override suspend fun insertSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.insert(
+ Contract.SelectionTable.URI,
+ ContentValues().apply {
+ put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
+ put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
+ }
+ )
+ }
+ }
+
+ override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SlotTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
+ val capacityColumnIndex =
+ cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
+ if (idColumnIndex == -1 || capacityColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Slot(
+ id = cursor.getString(idColumnIndex),
+ capacity = cursor.getInt(capacityColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.FlagsTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME)
+ val valueColumnIndex =
+ cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE)
+ if (nameColumnIndex == -1 || valueColumnIndex == -1) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Flag(
+ name = cursor.getString(nameColumnIndex),
+ value = cursor.getInt(valueColumnIndex) == 1,
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
+ return observeUri(Contract.SlotTable.URI).map { querySlots() }
+ }
+
+ override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
+ return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
+ }
+
+ override suspend fun queryAffordances():
+ List<KeyguardQuickAffordanceProviderClient.Affordance> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.AffordanceTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val idColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
+ val nameColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
+ val iconColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
+ val isEnabledColumnIndex =
+ cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
+ val enablementInstructionsColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
+ )
+ val enablementActionTextColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
+ )
+ val enablementComponentNameColumnIndex =
+ cursor.getColumnIndex(
+ Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
+ )
+ if (
+ idColumnIndex == -1 ||
+ nameColumnIndex == -1 ||
+ iconColumnIndex == -1 ||
+ isEnabledColumnIndex == -1 ||
+ enablementInstructionsColumnIndex == -1 ||
+ enablementActionTextColumnIndex == -1 ||
+ enablementComponentNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Affordance(
+ id = cursor.getString(idColumnIndex),
+ name = cursor.getString(nameColumnIndex),
+ iconResourceId = cursor.getInt(iconColumnIndex),
+ isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
+ enablementInstructions =
+ cursor
+ .getString(enablementInstructionsColumnIndex)
+ ?.split(
+ Contract.AffordanceTable
+ .ENABLEMENT_INSTRUCTIONS_DELIMITER
+ ),
+ enablementActionText =
+ cursor.getString(enablementActionTextColumnIndex),
+ enablementActionComponentName =
+ cursor.getString(enablementComponentNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeAffordances():
+ Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
+ return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
+ }
+
+ override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
+ return withContext(backgroundDispatcher) {
+ context.contentResolver
+ .query(
+ Contract.SelectionTable.URI,
+ null,
+ null,
+ null,
+ null,
+ )
+ ?.use { cursor ->
+ buildList {
+ val slotIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
+ val affordanceIdColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
+ val affordanceNameColumnIndex =
+ cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
+ if (
+ slotIdColumnIndex == -1 ||
+ affordanceIdColumnIndex == -1 ||
+ affordanceNameColumnIndex == -1
+ ) {
+ return@buildList
+ }
+
+ while (cursor.moveToNext()) {
+ add(
+ KeyguardQuickAffordanceProviderClient.Selection(
+ slotId = cursor.getString(slotIdColumnIndex),
+ affordanceId = cursor.getString(affordanceIdColumnIndex),
+ affordanceName = cursor.getString(affordanceNameColumnIndex),
+ )
+ )
+ }
+ }
+ }
+ }
+ ?: emptyList()
+ }
+
+ override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
+ return observeUri(Contract.SelectionTable.URI).map { querySelections() }
+ }
+
+ override suspend fun deleteSelection(
+ slotId: String,
+ affordanceId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
+ " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
+ arrayOf(
+ slotId,
+ affordanceId,
+ ),
+ )
+ }
+ }
+
+ override suspend fun deleteAllSelections(
+ slotId: String,
+ ) {
+ withContext(backgroundDispatcher) {
+ context.contentResolver.delete(
+ Contract.SelectionTable.URI,
+ Contract.SelectionTable.Columns.SLOT_ID,
+ arrayOf(
+ slotId,
+ ),
+ )
+ }
+ }
+
+ @SuppressLint("UseCompatLoadingForDrawables")
+ override suspend fun getAffordanceIcon(
+ @DrawableRes iconResourceId: Int,
+ tintColor: Int,
+ ): Drawable {
+ return withContext(backgroundDispatcher) {
+ context.packageManager
+ .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME)
+ .getDrawable(iconResourceId, context.theme)
+ .apply { setTint(tintColor) }
+ }
+ }
+
+ private fun observeUri(
+ uri: Uri,
+ ): Flow<Unit> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ context.contentResolver.registerContentObserver(
+ uri,
+ /* notifyForDescendants= */ true,
+ observer,
+ )
+
+ awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+ }
+ .onStart { emit(Unit) }
+ }
+
+ companion object {
+ private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
similarity index 98%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 98d8d3e..17be74b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.shared.keyguard.data.content
+package com.android.systemui.shared.quickaffordance.data.content
import android.content.ContentResolver
import android.net.Uri
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 100%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
rename to packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardQuickAffordanceSlots.kt
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 93c8073..1b0dacc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -166,15 +166,14 @@
counterLauncher.cleanUp(finishTransaction);
counterWallpaper.cleanUp(finishTransaction);
// Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- info.getChanges().get(i).getLeash().release();
- }
+ // before GC would.
+ info.releaseAllSurfaces();
// Don't release here since launcher might still be using them. Instead
// let launcher release them (eg. via RemoteAnimationTargets)
leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+ finishTransaction.close();
} catch (RemoteException e) {
Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+ " finished callback", e);
@@ -203,10 +202,13 @@
synchronized (mFinishRunnables) {
finishRunnable = mFinishRunnables.remove(mergeTarget);
}
+ // Since we're not actually animating, release native memory now
+ t.close();
+ info.releaseAllSurfaces();
if (finishRunnable == null) return;
onAnimationCancelled(false /* isKeyguardOccluded */);
finishRunnable.run();
}
};
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d4d3d25..b7e2494 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -126,15 +126,18 @@
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishedCallback) {
- if (!mergeTarget.equals(mToken)) return;
- if (!mRecentsSession.merge(info, t, recents)) return;
- try {
- finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
- } catch (RemoteException e) {
- Log.e(TAG, "Error merging transition.", e);
+ if (mergeTarget.equals(mToken) && mRecentsSession.merge(info, t, recents)) {
+ try {
+ finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error merging transition.", e);
+ }
+ // commit taskAppeared after merge transition finished.
+ mRecentsSession.commitTasksAppearedIfNeeded(recents);
+ } else {
+ t.close();
+ info.releaseAllSurfaces();
}
- // commit taskAppeared after merge transition finished.
- mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
return new RemoteTransition(remote, appThread);
@@ -248,6 +251,8 @@
}
// In this case, we are "returning" to an already running app, so just consume
// the merge and do nothing.
+ info.releaseAllSurfaces();
+ t.close();
return true;
}
final int layer = mInfo.getChanges().size() * 3;
@@ -264,6 +269,8 @@
t.setLayer(targets[i].leash, layer);
}
t.apply();
+ // not using the incoming anim-only surfaces
+ info.releaseAnimSurfaces();
mAppearedTargets = targets;
return true;
}
@@ -380,9 +387,7 @@
}
// Only release the non-local created surface references. The animator is responsible
// for releasing the leashes created by local.
- for (int i = 0; i < mInfo.getChanges().size(); ++i) {
- mInfo.getChanges().get(i).getLeash().release();
- }
+ mInfo.releaseAllSurfaces();
// Reset all members.
mWrapped = null;
mFinishCB = null;
diff --git a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
index c5190e8..ea808eb 100644
--- a/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
+++ b/packages/SystemUI/src/com/android/keyguard/EmergencyButtonController.java
@@ -135,7 +135,7 @@
mPowerManager.userActivity(SystemClock.uptimeMillis(), true);
}
mActivityTaskManager.stopSystemLockTaskMode();
- mShadeController.collapsePanel(false);
+ mShadeController.collapseShade(false);
if (mTelecomManager != null && mTelecomManager.isInCall()) {
mTelecomManager.showInCallScreen(false);
if (mEmergencyButtonCallback != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index d60cc75..50449b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -52,6 +52,7 @@
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -183,15 +184,18 @@
private final AccessibilityManager mA11yManager;
private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
private final NotificationShadeWindowController mNotificationShadeController;
+ private final ShadeController mShadeController;
private final StatusBarWindowCallback mNotificationShadeCallback;
private boolean mDismissNotificationShadeActionRegistered;
@Inject
public SystemActions(Context context,
NotificationShadeWindowController notificationShadeController,
+ ShadeController shadeController,
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Optional<Recents> recentsOptional) {
mContext = context;
+ mShadeController = shadeController;
mRecentsOptional = recentsOptional;
mReceiver = new SystemActionsBroadcastReceiver();
mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
@@ -529,9 +533,7 @@
}
private void handleAccessibilityDismissNotificationShade() {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- centralSurfaces -> centralSurfaces.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE, false /* force */));
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
private void handleDpadUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
*
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
*
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+ private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+ "systemui.keyguard.quickaffordance.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
- private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+ const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
}
val keys = PeopleBackupHelper.getFilesToBackup()
- addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
- this, userHandle, keys.toTypedArray()))
+ addHelper(
+ PEOPLE_TILES_BACKUP_KEY,
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ )
+ addHelper(
+ KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+ KeyguardQuickAffordanceBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ ),
+ )
}
override fun onRestoreFinished() {
super.onRestoreFinished()
- val intent = Intent(ACTION_RESTORE_FINISHED).apply {
- `package` = packageName
- putExtra(Intent.EXTRA_USER_ID, userId)
- flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
- }
+ val intent =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
}
@@ -90,7 +104,9 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ * ```
* actions to take
+ * ```
*/
private class NoOverwriteFileBackupHelper(
val lock: Any,
@@ -115,23 +131,23 @@
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) {
- synchronized(lock) {
- super.performBackup(oldState, data, newState)
- }
+ synchronized(lock) { super.performBackup(oldState, data, newState) }
}
}
}
+
private fun getPPControlsFile(context: Context): () -> Unit {
return {
val filesDir = context.filesDir
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = Environment.buildPath(filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+ val dest =
+ Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
- AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+ AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
* from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
* a given broadcast.
*
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
* Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
* Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
* and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index aa6c619..2d558ad 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -346,6 +346,12 @@
// TODO(b/256873975): Tracking Bug
@JvmField @Keep val WM_BUBBLE_BAR = unreleasedFlag(1111, "wm_bubble_bar")
+ // TODO(b/260271148): Tracking bug
+ @Keep
+ @JvmField
+ val WM_DESKTOP_WINDOWING_2 =
+ sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+
// 1200 - predictive back
@Keep
@JvmField
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
index 29febb6..4ae37c5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt
@@ -29,7 +29,7 @@
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 0214313..e631816 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -220,6 +220,7 @@
synchronized (mFinishCallbacks) {
if (mFinishCallbacks.remove(transition) == null) return;
}
+ info.releaseAllSurfaces();
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -235,6 +236,8 @@
synchronized (mFinishCallbacks) {
origFinishCB = mFinishCallbacks.remove(transition);
}
+ info.releaseAllSurfaces();
+ t.close();
if (origFinishCB == null) {
// already finished (or not started yet), so do nothing.
return;
@@ -423,12 +426,15 @@
t.apply();
mBinder.setOccluded(true /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
@@ -440,12 +446,15 @@
t.apply();
mBinder.setOccluded(false /* isOccluded */, true /* animate */);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ info.releaseAllSurfaces();
}
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ t.close();
+ info.releaseAllSurfaces();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5ed3ba7..948239a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -870,7 +870,7 @@
@Override
public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (launchIsFullScreen) {
- mCentralSurfaces.instantCollapseNotificationPanel();
+ mShadeController.get().instantCollapseShade();
}
mOccludeAnimationPlaying = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
index 3c09aab..dbc376e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfig.kt
@@ -26,14 +26,17 @@
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import dagger.Lazy
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
-import javax.inject.Inject
@SysUISingleton
-class CameraQuickAffordanceConfig @Inject constructor(
- @Application private val context: Context,
- private val cameraGestureHelper: CameraGestureHelper,
+class CameraQuickAffordanceConfig
+@Inject
+constructor(
+ @Application private val context: Context,
+ private val cameraGestureHelper: Lazy<CameraGestureHelper>,
) : KeyguardQuickAffordanceConfig {
override val key: String
@@ -46,17 +49,23 @@
get() = com.android.internal.R.drawable.perm_group_camera
override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState>
- get() = flowOf(
- KeyguardQuickAffordanceConfig.LockScreenState.Visible(
- icon = Icon.Resource(
+ get() =
+ flowOf(
+ KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+ icon =
+ Icon.Resource(
com.android.internal.R.drawable.perm_group_camera,
ContentDescription.Resource(R.string.accessibility_camera_button)
- )
+ )
+ )
)
- )
- override fun onTriggered(expandable: Expandable?): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- cameraGestureHelper.launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ override fun onTriggered(
+ expandable: Expandable?
+ ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+ cameraGestureHelper
+ .get()
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 4477310..98b1a73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import kotlinx.coroutines.flow.Flow
/** Defines interface that can act as data source for a single quick affordance model. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index b29cf45..4f37e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -28,14 +30,18 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceSelectionManager
@Inject
@@ -43,15 +49,10 @@
@Application context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- private val sharedPrefs: SharedPreferences
- get() =
- userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
private val userId: Flow<Int> = conflatedCallbackFlow {
val callback =
@@ -78,21 +79,54 @@
}
}
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
val selections: Flow<Map<String, List<String>>> =
- userId.flatMapLatest {
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even
+ // if we never get a Backup & Restore restoration event (which is the most
+ // common
+ // case anyway as restoration really only happens on initial device setup).
+ emit(Unit)
+ }
+ ) { _, _ ->
}
- }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user
+ // ID changes or we have a backup & restore restoration event. The reason is
+ // that
+ // our sharedPrefs instance needs to be replaced with a new one as it depends on
+ // the
+ // user ID and when the B&R job completes, the backing file is replaced but the
+ // existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
@@ -144,9 +178,17 @@
sharedPrefs.edit().putString(key, value).apply()
}
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceSelectionManager"
- @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ const val FILE_NAME = "quick_affordance_selections"
private const val KEY_PREFIX_SLOT = "slot_"
private const val SLOT_AFFORDANCES_DELIMITER = ":"
private const val AFFORDANCE_DELIMITER = ","
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ if (UserFileManagerImpl.isPrimaryUser(userId)) {
+ KeyguardQuickAffordanceSelectionManager.FILE_NAME
+ } else {
+ UserFileManagerImpl.secondaryUserFile(
+ context = context,
+ fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+ directoryName = UserFileManagerImpl.SHARED_PREFS,
+ userId = userId,
+ )
+ .also { UserFileManagerImpl.ensureParentDirExists(it) }
+ .toString()
+ }
+ )
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 2d94d76..ee7154f 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
@@ -34,8 +34,8 @@
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index b252be1..f7a9bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -1053,18 +1053,9 @@
rootOverlay!!.add(mediaFrame)
} else {
val targetHost = getHost(newLocation)!!.hostView
- // When adding back to the host, let's make sure to reset the bounds.
- // Usually adding the view will trigger a layout that does this automatically,
- // but we sometimes suppress this.
+ // This will either do a full layout pass and remeasure, or it will bypass
+ // that and directly set the mediaFrame's bounds within the premeasured host.
targetHost.addView(mediaFrame)
- val left = targetHost.paddingLeft
- val top = targetHost.paddingTop
- mediaFrame.setLeftTopRightBottom(
- left,
- top,
- left + currentBounds.width(),
- top + currentBounds.height()
- )
if (mediaFrame.childCount > 0) {
val child = mediaFrame.getChildAt(0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 4bf3031..4feb984 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -420,7 +420,9 @@
*/
fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? =
traceSection("MediaViewController#getMeasurementsForState") {
- val viewState = obtainViewState(hostState) ?: return null
+ // measurements should never factor in the squish fraction
+ val viewState =
+ obtainViewState(hostState.copy().also { it.squishFraction = 1.0f }) ?: return null
measurement.measuredWidth = viewState.width
measurement.measuredHeight = viewState.height
return measurement
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 314252b..4c9c99c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -36,6 +36,7 @@
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.tiles.UserDetailView
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.dialog.DialogShowerImpl
import javax.inject.Inject
import javax.inject.Provider
@@ -130,19 +131,6 @@
}
}
- private class DialogShowerImpl(
- private val animateFrom: Dialog,
- private val dialogLaunchAnimator: DialogLaunchAnimator
- ) : DialogInterface by animateFrom, DialogShower {
- override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
- dialogLaunchAnimator.showFromDialog(
- dialog,
- animateFrom = animateFrom,
- cuj
- )
- }
- }
-
interface DialogShower : DialogInterface {
fun showDialog(dialog: Dialog, cuj: DialogCuj)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 10d31ea..57b256e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -84,6 +84,8 @@
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 androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -279,6 +281,13 @@
private final ActionIntentExecutor mActionExecutor;
private final UserManager mUserManager;
+ private final OnBackInvokedCallback mOnBackInvokedCallback = () -> {
+ if (DEBUG_INPUT) {
+ Log.d(TAG, "Predictive Back callback dispatched");
+ }
+ respondToBack();
+ };
+
private ScreenshotView mScreenshotView;
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
@@ -465,6 +474,10 @@
}
}
+ private void respondToBack() {
+ dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ }
+
/**
* Update resources on configuration change. Reinflate for theme/color changes.
*/
@@ -476,6 +489,26 @@
// Inflate the screenshot layout
mScreenshotView = (ScreenshotView)
LayoutInflater.from(mContext).inflate(R.layout.screenshot, null);
+ 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() {
@Override
public void onUserInteraction() {
@@ -503,7 +536,7 @@
if (DEBUG_INPUT) {
Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
}
- dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
+ respondToBack();
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
import javax.inject.Inject
/**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
*/
@SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
// Context of system process and system user.
private val context: Context,
val userManager: UserManager,
@@ -49,80 +51,114 @@
) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
- @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
- }
- private val broadcastReceiver = object : BroadcastReceiver() {
+ /** Returns `true` if the given user ID is that for the primary/system user. */
+ fun isPrimaryUser(userId: Int): Boolean {
+ return UserHandle(userId).isSystem
+ }
+
/**
- * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ * Returns a [File] pointing to the correct path for a secondary user ID.
+ *
+ * Note that there is no check for the type of user. This should only be called for
+ * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+ *
+ * Note also that there is no guarantee that the parent directory structure for the file
+ * exists on disk. For that, call [ensureParentDirExists].
+ *
+ * @param context The context
+ * @param fileName The name of the file
+ * @param directoryName The name of the directory that would contain the file
+ * @param userId The ID of the user to build a file path for
*/
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_REMOVED) {
- clearDeletedUserData()
+ fun secondaryUserFile(
+ context: Context,
+ fileName: String,
+ directoryName: String,
+ userId: Int,
+ ): File {
+ return Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ directoryName,
+ fileName,
+ )
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
}
}
}
- /**
- * Poll for user-specific directories to delete upon start up.
- */
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /** Poll for user-specific directories to delete upon start up. */
override fun start() {
clearDeletedUserData()
- val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_REMOVED)
- }
+ val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
- /**
- * Return the file based on current user.
- */
+ /** Return the file based on current user. */
override fun getFile(fileName: String, userId: Int): File {
- return if (UserHandle(userId).isSystem) {
- Environment.buildPath(
- context.filesDir,
- fileName
- )
+ return if (isPrimaryUser(userId)) {
+ Environment.buildPath(context.filesDir, fileName)
} else {
- val secondaryFile = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- FILES,
- fileName
- )
+ val secondaryFile =
+ secondaryUserFile(
+ context = context,
+ userId = userId,
+ directoryName = FILES,
+ fileName = fileName,
+ )
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
- /**
- * Get shared preferences from user.
- */
+ /** Get shared preferences from user. */
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
- if (UserHandle(userId).isSystem) {
+ if (isPrimaryUser(userId)) {
return context.getSharedPreferences(fileName, mode)
}
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- SHARED_PREFS,
- fileName
- )
+
+ val secondaryUserDir =
+ secondaryUserFile(
+ context = context,
+ fileName = fileName,
+ directoryName = SHARED_PREFS,
+ userId = userId,
+ )
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
- /**
- * Remove dirs for deleted users.
- */
+ /** Remove dirs for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
@@ -133,10 +169,11 @@
dirsToDelete.forEach { dir ->
try {
- val dirToDelete = Environment.buildPath(
- file,
- dir,
- )
+ val dirToDelete =
+ Environment.buildPath(
+ file,
+ dir,
+ )
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
}
}
}
-
- /**
- * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
- * recursively.
- */
- @VisibleForTesting
- internal fun ensureParentDirExists(file: File) {
- val parent = file.parentFile
- if (!parent.exists()) {
- if (!parent.mkdirs()) {
- Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index aa610bd..de9dcf9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -16,6 +16,9 @@
package com.android.systemui.shade;
+import android.view.MotionEvent;
+
+import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -29,31 +32,32 @@
*/
public interface ShadeController {
- /**
- * Make our window larger and the panel expanded
- */
- void instantExpandNotificationsPanel();
+ /** Make our window larger and the shade expanded */
+ void instantExpandShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels();
+ /** Collapse the shade instantly with no animation. */
+ void instantCollapseShade();
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags);
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShade(int flags);
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeForced();
+
+ /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ void animateCollapseShadeDelayed();
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
- * dismissing {@link CentralSurfaces} when on {@link StatusBarState#SHADE}.
+ * dismissing status bar when on {@link StatusBarState#SHADE}.
*/
- void animateCollapsePanels(int flags, boolean force);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
- void animateCollapsePanels(int flags, boolean force, boolean delayed);
-
- /** See {@link #animateCollapsePanels(int, boolean)}. */
void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
/**
- * If the notifications panel is not fully expanded, collapse it animated.
+ * If the shade is not fully expanded, collapse it animated.
*
* @return Seems to always return false
*/
@@ -77,9 +81,7 @@
*/
void addPostCollapseAction(Runnable action);
- /**
- * Run all of the runnables added by {@link #addPostCollapseAction}.
- */
+ /** Run all of the runnables added by {@link #addPostCollapseAction}. */
void runPostCollapseRunnables();
/**
@@ -87,13 +89,48 @@
*
* @return true if the shade was open, else false
*/
- boolean collapsePanel();
+ boolean collapseShade();
/**
- * If animate is true, does the same as {@link #collapsePanel()}. Otherwise, instantly collapse
- * the panel. Post collapse runnables will be executed
+ * If animate is true, does the same as {@link #collapseShade()}. Otherwise, instantly collapse
+ * the shade. Post collapse runnables will be executed
*
* @param animate true to animate the collapse, false for instantaneous collapse
*/
- void collapsePanel(boolean animate);
+ void collapseShade(boolean animate);
+
+ /** Makes shade expanded but not visible. */
+ void makeExpandedInvisible();
+
+ /** Makes shade expanded and visible. */
+ void makeExpandedVisible(boolean force);
+
+ /** Returns whether the shade is expanded and visible. */
+ boolean isExpandedVisible();
+
+ /** Handle status bar touch event. */
+ void onStatusBarTouch(MotionEvent event);
+
+ /** Sets the listener for when the visibility of the shade changes. */
+ void setVisibilityListener(ShadeVisibilityListener listener);
+
+ /** */
+ void setNotificationPresenter(NotificationPresenter presenter);
+
+ /** */
+ void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController notificationShadeWindowViewController);
+
+ /** */
+ void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController);
+
+ /** Listens for shade visibility changes. */
+ interface ShadeVisibilityListener {
+ /** Called when the visibility of the shade changes. */
+ void visibilityChanged(boolean visible);
+
+ /** Called when shade expanded and visible state changed. */
+ void expandedVisibleChanged(boolean expandedVisible);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d783293..807e2e6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -16,9 +16,12 @@
package com.android.systemui.shade;
+import android.content.ComponentCallbacks2;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
@@ -27,11 +30,12 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.window.StatusBarWindowController;
import java.util.ArrayList;
-import java.util.Optional;
import javax.inject.Inject;
@@ -39,68 +43,81 @@
/** An implementation of {@link ShadeController}. */
@SysUISingleton
-public class ShadeControllerImpl implements ShadeController {
+public final class ShadeControllerImpl implements ShadeController {
private static final String TAG = "ShadeControllerImpl";
private static final boolean SPEW = false;
- private final CommandQueue mCommandQueue;
- private final StatusBarStateController mStatusBarStateController;
- protected final NotificationShadeWindowController mNotificationShadeWindowController;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final int mDisplayId;
- protected final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
+
+ private final CommandQueue mCommandQueue;
+ private final KeyguardStateController mKeyguardStateController;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final StatusBarStateController mStatusBarStateController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final StatusBarWindowController mStatusBarWindowController;
+
private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Lazy<NotificationGutsManager> mGutsManager;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private boolean mExpandedVisible;
+
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private NotificationPresenter mPresenter;
+ private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private ShadeVisibilityListener mShadeVisibilityListener;
+
@Inject
public ShadeControllerImpl(
CommandQueue commandQueue,
+ KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
- NotificationShadeWindowController notificationShadeWindowController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBarWindowController statusBarWindowController,
+ NotificationShadeWindowController notificationShadeWindowController,
WindowManager windowManager,
- Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
- Lazy<AssistManager> assistManagerLazy
+ Lazy<AssistManager> assistManagerLazy,
+ Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
+ mStatusBarWindowController = statusBarWindowController;
+ mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mDisplayId = windowManager.getDefaultDisplay().getDisplayId();
- // TODO: Remove circular reference to CentralSurfaces when possible.
- mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+ mKeyguardStateController = keyguardStateController;
mAssistManagerLazy = assistManagerLazy;
}
@Override
- public void instantExpandNotificationsPanel() {
+ public void instantExpandShade() {
// Make our window larger and the panel expanded.
- getCentralSurfaces().makeExpandedVisible(true /* force */);
- getNotificationPanelViewController().expand(false /* animate */);
+ makeExpandedVisible(true /* force */);
+ mNotificationPanelViewController.expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
@Override
- public void animateCollapsePanels() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ public void animateCollapseShade() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
}
@Override
- public void animateCollapsePanels(int flags) {
- animateCollapsePanels(flags, false /* force */, false /* delayed */,
- 1.0f /* speedUpFactor */);
+ public void animateCollapseShade(int flags) {
+ animateCollapsePanels(flags, false, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- animateCollapsePanels(flags, force, false /* delayed */, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeForced() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force, boolean delayed) {
- animateCollapsePanels(flags, force, delayed, 1.0f /* speedUpFactor */);
+ public void animateCollapseShadeDelayed() {
+ animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
}
@Override
@@ -111,34 +128,26 @@
return;
}
if (SPEW) {
- Log.d(TAG, "animateCollapse():"
- + " mExpandedVisible=" + getCentralSurfaces().isExpandedVisible()
- + " flags=" + flags);
+ Log.d(TAG,
+ "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags);
}
-
- // TODO(b/62444020): remove when this bug is fixed
- Log.v(TAG, "NotificationShadeWindow: " + getNotificationShadeWindowView()
- + " canPanelBeCollapsed(): "
- + getNotificationPanelViewController().canPanelBeCollapsed());
if (getNotificationShadeWindowView() != null
- && getNotificationPanelViewController().canPanelBeCollapsed()
+ && mNotificationPanelViewController.canPanelBeCollapsed()
&& (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) {
// release focus immediately to kick off focus change transition
mNotificationShadeWindowController.setNotificationShadeFocusable(false);
- getCentralSurfaces().getNotificationShadeWindowViewController().cancelExpandHelper();
- getNotificationPanelViewController()
- .collapsePanel(true /* animate */, delayed, speedUpFactor);
+ mNotificationShadeWindowViewController.cancelExpandHelper();
+ mNotificationPanelViewController.collapsePanel(true, delayed, speedUpFactor);
}
}
-
@Override
public boolean closeShadeIfOpen() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
- getCentralSurfaces().visibilityChanged(false);
+ notifyVisibilityChanged(false);
mAssistManagerLazy.get().hideAssist();
}
return false;
@@ -146,21 +155,19 @@
@Override
public boolean isShadeOpen() {
- NotificationPanelViewController controller =
- getNotificationPanelViewController();
- return controller.isExpanding() || controller.isFullyExpanded();
+ return mNotificationPanelViewController.isExpanding()
+ || mNotificationPanelViewController.isFullyExpanded();
}
@Override
public void postOnShadeExpanded(Runnable executable) {
- getNotificationPanelViewController().addOnGlobalLayoutListener(
+ mNotificationPanelViewController.addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
- if (getCentralSurfaces().getNotificationShadeWindowView()
- .isVisibleToUser()) {
- getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
- getNotificationPanelViewController().postToView(executable);
+ if (getNotificationShadeWindowView().isVisibleToUser()) {
+ mNotificationPanelViewController.removeOnGlobalLayoutListener(this);
+ mNotificationPanelViewController.postToView(executable);
}
}
});
@@ -183,12 +190,11 @@
}
@Override
- public boolean collapsePanel() {
- if (!getNotificationPanelViewController().isFullyCollapsed()) {
+ public boolean collapseShade() {
+ if (!mNotificationPanelViewController.isFullyCollapsed()) {
// close the shade if it was open
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed */);
- getCentralSurfaces().visibilityChanged(false);
+ animateCollapseShadeDelayed();
+ notifyVisibilityChanged(false);
return true;
} else {
@@ -197,33 +203,131 @@
}
@Override
- public void collapsePanel(boolean animate) {
+ public void collapseShade(boolean animate) {
if (animate) {
- boolean willCollapse = collapsePanel();
+ boolean willCollapse = collapseShade();
if (!willCollapse) {
runPostCollapseRunnables();
}
- } else if (!getPresenter().isPresenterFullyCollapsed()) {
- getCentralSurfaces().instantCollapseNotificationPanel();
- getCentralSurfaces().visibilityChanged(false);
+ } else if (!mPresenter.isPresenterFullyCollapsed()) {
+ instantCollapseShade();
+ notifyVisibilityChanged(false);
} else {
runPostCollapseRunnables();
}
}
- private CentralSurfaces getCentralSurfaces() {
- return mCentralSurfacesOptionalLazy.get().get();
+ @Override
+ public void onStatusBarTouch(MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ if (mExpandedVisible) {
+ animateCollapseShade();
+ }
+ }
}
- private NotificationPresenter getPresenter() {
- return getCentralSurfaces().getPresenter();
+ @Override
+ public void instantCollapseShade() {
+ mNotificationPanelViewController.instantCollapse();
+ runPostCollapseRunnables();
}
- protected NotificationShadeWindowView getNotificationShadeWindowView() {
- return getCentralSurfaces().getNotificationShadeWindowView();
+ @Override
+ public void makeExpandedVisible(boolean force) {
+ if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
+ if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
+ return;
+ }
+
+ mExpandedVisible = true;
+
+ // Expand the window to encompass the full screen in anticipation of the drag.
+ // It's only possible to do atomically because the status bar is at the top of the screen!
+ mNotificationShadeWindowController.setPanelVisible(true);
+
+ notifyVisibilityChanged(true);
+ mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
+ notifyExpandedVisibleChanged(true);
}
- private NotificationPanelViewController getNotificationPanelViewController() {
- return getCentralSurfaces().getNotificationPanelViewController();
+ @Override
+ public void makeExpandedInvisible() {
+ if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
+
+ if (!mExpandedVisible || getNotificationShadeWindowView() == null) {
+ return;
+ }
+
+ // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
+ mNotificationPanelViewController.collapsePanel(false, false, 1.0f);
+
+ mNotificationPanelViewController.closeQs();
+
+ mExpandedVisible = false;
+ notifyVisibilityChanged(false);
+
+ // Update the visibility of notification shade and status bar window.
+ mNotificationShadeWindowController.setPanelVisible(false);
+ mStatusBarWindowController.setForceStatusBarVisible(false);
+
+ // Close any guts that might be visible
+ mGutsManager.get().closeAndSaveGuts(
+ true /* removeLeavebehind */,
+ true /* force */,
+ true /* removeControls */,
+ -1 /* x */,
+ -1 /* y */,
+ true /* resetMenu */);
+
+ runPostCollapseRunnables();
+ notifyExpandedVisibleChanged(false);
+ mCommandQueue.recomputeDisableFlags(
+ mDisplayId,
+ mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
+
+ // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
+ // the bouncer appear animation.
+ if (!mKeyguardStateController.isShowing()) {
+ WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+ }
+ }
+
+ @Override
+ public boolean isExpandedVisible() {
+ return mExpandedVisible;
+ }
+
+ @Override
+ public void setVisibilityListener(ShadeVisibilityListener listener) {
+ mShadeVisibilityListener = listener;
+ }
+
+ private void notifyVisibilityChanged(boolean visible) {
+ mShadeVisibilityListener.visibilityChanged(visible);
+ }
+
+ private void notifyExpandedVisibleChanged(boolean expandedVisible) {
+ mShadeVisibilityListener.expandedVisibleChanged(expandedVisible);
+ }
+
+ @Override
+ public void setNotificationPresenter(NotificationPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ public void setNotificationShadeWindowViewController(
+ NotificationShadeWindowViewController controller) {
+ mNotificationShadeWindowViewController = controller;
+ }
+
+ private NotificationShadeWindowView getNotificationShadeWindowView() {
+ return mNotificationShadeWindowViewController.getView();
+ }
+
+ @Override
+ public void setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelViewController = notificationPanelViewController;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index 143c697..bd5b8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -94,7 +94,11 @@
private val views: Sequence<View>
get() = if (!this::tl.isInitialized) sequenceOf() else sequenceOf(tl, tr, br, bl)
- private var showingListener: ShowingListener? = null
+ var showingListener: ShowingListener? = null
+ set(value) {
+ field = value
+ }
+ get() = field
init {
contentInsetsProvider.addCallback(object : StatusBarContentInsetsChangedListener {
@@ -147,10 +151,6 @@
return uiExecutor
}
- fun setShowingListener(l: ShowingListener?) {
- showingListener = l
- }
-
@UiThread
fun setNewRotation(rot: Int) {
dlog("updateRotation: $rot")
@@ -219,7 +219,7 @@
// Update the gravity and margins of the privacy views
@UiThread
- private fun updateRotations(rotation: Int, paddingTop: Int) {
+ open fun updateRotations(rotation: Int, paddingTop: Int) {
// To keep a view in the corner, its gravity is always the description of its current corner
// Therefore, just figure out which view is in which corner. This turns out to be something
// like (myCorner - rot) mod 4, where topLeft = 0, topRight = 1, etc. and portrait = 0, and
@@ -250,7 +250,7 @@
}
@UiThread
- private fun setCornerSizes(state: ViewState) {
+ open fun setCornerSizes(state: ViewState) {
// StatusBarContentInsetsProvider can tell us the location of the privacy indicator dot
// in every rotation. The only thing we need to check is rtl
val rtl = state.layoutRtl
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 64f87ca..b56bae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -54,8 +54,6 @@
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import java.util.Collections;
-
import javax.inject.Inject;
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 0ce9656..f21db0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -154,7 +154,7 @@
// If the user selected Priority and the previous selection was not priority, show a
// People Tile add request.
if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
}
mGutsContainer.closeControls(v, /* save= */ true);
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 073bd4b..b519aef 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
@@ -1401,10 +1401,10 @@
mExpandedHeight = height;
setIsExpanded(height > 0);
int minExpansionHeight = getMinExpansionHeight();
- if (height < minExpansionHeight) {
+ if (height < minExpansionHeight && !mShouldUseSplitNotificationShade) {
mClipRect.left = 0;
mClipRect.right = getWidth();
- mClipRect.top = getNotificationsClippingTopBound();
+ mClipRect.top = 0;
mClipRect.bottom = (int) height;
height = minExpansionHeight;
setRequestedClipBounds(mClipRect);
@@ -1466,17 +1466,6 @@
notifyAppearChangedListeners();
}
- private int getNotificationsClippingTopBound() {
- if (isHeadsUpTransition()) {
- // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
- // to give it extra clipping margin. Because clipping has rounded corners, we also
- // need to account for that corner clipping.
- return -mAmbientState.getStackTopMargin() - mCornerRadius;
- } else {
- return 0;
- }
- }
-
private void notifyAppearChangedListeners() {
float appear;
float expandAmount;
@@ -4236,7 +4225,7 @@
mShadeNeedsToClose = false;
postDelayed(
() -> {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
},
DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
}
@@ -5139,6 +5128,7 @@
println(pw, "intrinsicPadding", mIntrinsicPadding);
println(pw, "topPadding", mTopPadding);
println(pw, "bottomPadding", mBottomPadding);
+ mNotificationStackSizeCalculator.dump(pw, args);
});
pw.println();
pw.println("Contents:");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index ae854e2..25f99c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -30,6 +30,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.Compile
import com.android.systemui.util.children
+import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.min
@@ -53,6 +54,8 @@
@Main private val resources: Resources
) {
+ private lateinit var lastComputeHeightLog : String
+
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -114,7 +117,9 @@
shelfIntrinsicHeight: Float
): Int {
log { "\n" }
- val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ false)
var maxNotifications =
stackHeightSequence.lastIndexWhile { heightResult ->
@@ -157,18 +162,21 @@
shelfIntrinsicHeight: Float
): Float {
log { "\n" }
+ lastComputeHeightLog = ""
val heightPerMaxNotifications =
- computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight)
+ computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
+ /* computeHeight= */ true)
val (notificationsHeight, shelfHeightWithSpaceBefore) =
heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
heightPerMaxNotifications.last() // Height with all notifications visible.
}
- log {
- "computeHeight(maxNotifications=$maxNotifications," +
+ lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
"shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
"${notificationsHeight + shelfHeightWithSpaceBefore}" +
" = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
+ log {
+ lastComputeHeightLog
}
return notificationsHeight + shelfHeightWithSpaceBefore
}
@@ -184,7 +192,8 @@
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
- shelfHeight: Float
+ shelfHeight: Float,
+ computeHeight: Boolean
): Sequence<StackHeight> = sequence {
log { "computeHeightPerNotificationLimit" }
@@ -213,9 +222,14 @@
currentIndex = firstViewInShelfIndex)
spaceBeforeShelf + shelfHeight
}
+
+ val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
+ "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ if (computeHeight) {
+ lastComputeHeightLog += "\n" + currentLog
+ }
log {
- "i=$i notificationsHeight=$notifications " +
- "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
+ currentLog
}
yield(
StackHeight(
@@ -260,6 +274,10 @@
return size
}
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+ }
+
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
if (visibility == GONE || hasNoContentHeight()) return false
if (onLockscreen) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 01ca667..0ec7c62 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -193,8 +193,6 @@
void animateExpandSettingsPanel(@Nullable String subpanel);
- void animateCollapsePanels(int flags, boolean force);
-
void collapsePanelOnMainThread();
void togglePanel();
@@ -282,8 +280,6 @@
void postAnimateOpenPanels();
- boolean isExpandedVisible();
-
boolean isPanelExpanded();
void onInputFocusTransfer(boolean start, boolean cancel, float velocity);
@@ -495,12 +491,13 @@
void updateNotificationPanelTouchState();
+ /**
+ * TODO(b/257041702) delete this
+ * @deprecated Use ShadeController#makeExpandedVisible
+ */
+ @Deprecated
void makeExpandedVisible(boolean force);
- void instantCollapseNotificationPanel();
-
- void visibilityChanged(boolean visible);
-
int getDisplayId();
int getRotation();
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 f3482f4..6b72e96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -209,7 +209,7 @@
public void animateExpandNotificationsPanel() {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -222,7 +222,7 @@
public void animateExpandSettingsPanel(@Nullable String subPanel) {
if (CentralSurfaces.SPEW) {
Log.d(CentralSurfaces.TAG,
- "animateExpand: mExpandedVisible=" + mCentralSurfaces.isExpandedVisible());
+ "animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
if (!mCommandQueue.panelsEnabled()) {
return;
@@ -276,7 +276,7 @@
if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) {
if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -293,7 +293,7 @@
if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
mCentralSurfaces.updateQsExpansionEnabled();
if ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -550,7 +550,7 @@
@Override
public void togglePanel() {
if (mCentralSurfaces.isPanelExpanded()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
} else {
animateExpandNotificationsPanel();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 32ea8d6..d988772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -58,7 +58,6 @@
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -406,12 +405,6 @@
/** */
@Override
- public void animateCollapsePanels(int flags, boolean force) {
- mCommandQueueCallbacks.animateCollapsePanels(flags, force);
- }
-
- /** */
- @Override
public void togglePanel() {
mCommandQueueCallbacks.togglePanel();
}
@@ -493,8 +486,6 @@
private View mReportRejectedTouch;
- private boolean mExpandedVisible;
-
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
@@ -893,6 +884,8 @@
updateDisplaySize();
mStatusBarHideIconsForBouncerManager.setDisplayId(mDisplayId);
+ initShadeVisibilityListener();
+
// start old BaseStatusBar.start().
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
@@ -1083,6 +1076,25 @@
requestTopUi, componentTag))));
}
+ @VisibleForTesting
+ void initShadeVisibilityListener() {
+ mShadeController.setVisibilityListener(new ShadeController.ShadeVisibilityListener() {
+ @Override
+ public void visibilityChanged(boolean visible) {
+ onShadeVisibilityChanged(visible);
+ }
+
+ @Override
+ public void expandedVisibleChanged(boolean expandedVisible) {
+ if (expandedVisible) {
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
+ } else {
+ onExpandedInvisible();
+ }
+ }
+ });
+ }
+
private void onFoldedStateChanged(boolean isFolded, boolean willGoToSleep) {
Trace.beginSection("CentralSurfaces#onFoldedStateChanged");
onFoldedStateChangedInternal(isFolded, willGoToSleep);
@@ -1228,7 +1240,7 @@
mNotificationPanelViewController.initDependencies(
this,
- this::makeExpandedInvisible,
+ mShadeController::makeExpandedInvisible,
mNotificationShelfController);
BackDropView backdrop = mNotificationShadeWindowView.findViewById(R.id.backdrop);
@@ -1431,6 +1443,7 @@
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
mStackScrollerController.setNotificationActivityStarter(mNotificationActivityStarter);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
+ mShadeController.setNotificationPresenter(mPresenter);
mNotificationsController.initialize(
this,
mPresenter,
@@ -1480,11 +1493,7 @@
return (v, event) -> {
mAutoHideController.checkUserAutoHide(event);
mRemoteInputManager.checkRemoteInputOutside(event);
- if (event.getAction() == MotionEvent.ACTION_UP) {
- if (mExpandedVisible) {
- mShadeController.animateCollapsePanels();
- }
- }
+ mShadeController.onStatusBarTouch(event);
return mNotificationShadeWindowView.onTouchEvent(event);
};
}
@@ -1506,6 +1515,9 @@
mNotificationShadeWindowViewController.setupExpandedStatusBar();
mNotificationPanelViewController =
mCentralSurfacesComponent.getNotificationPanelViewController();
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
mCentralSurfacesComponent.getLockIconViewController().init();
mStackScrollerController =
mCentralSurfacesComponent.getNotificationStackScrollLayoutController();
@@ -1827,7 +1839,7 @@
&& isLaunchForActivity) {
onClosingFinished();
} else {
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
}
}
@@ -1838,7 +1850,7 @@
onClosingFinished();
}
if (launchIsFullScreen) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
}
@@ -1928,33 +1940,13 @@
}
@Override
- public void makeExpandedVisible(boolean force) {
- if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible);
- if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) {
- return;
- }
-
- mExpandedVisible = true;
-
- // Expand the window to encompass the full screen in anticipation of the drag.
- // This is only possible to do atomically because the status bar is at the top of the screen!
- mNotificationShadeWindowController.setPanelVisible(true);
-
- visibilityChanged(true);
- mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */);
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
- }
-
- @Override
public void postAnimateCollapsePanels() {
- mMainExecutor.execute(mShadeController::animateCollapsePanels);
+ mMainExecutor.execute(mShadeController::animateCollapseShade);
}
@Override
public void postAnimateForceCollapsePanels() {
- mMainExecutor.execute(
- () -> mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE,
- true /* force */));
+ mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
}
@Override
@@ -1963,11 +1955,6 @@
}
@Override
- public boolean isExpandedVisible() {
- return mExpandedVisible;
- }
-
- @Override
public boolean isPanelExpanded() {
return mPanelExpanded;
}
@@ -1996,46 +1983,13 @@
}
}
- void makeExpandedInvisible() {
- if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
-
- if (!mExpandedVisible || mNotificationShadeWindowView == null) {
- return;
- }
-
- // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868)
- mNotificationPanelViewController.collapsePanel(/*animate=*/ false, false /* delayed*/,
- 1.0f /* speedUpFactor */);
-
- mNotificationPanelViewController.closeQs();
-
- mExpandedVisible = false;
- visibilityChanged(false);
-
- // Update the visibility of notification shade and status bar window.
- mNotificationShadeWindowController.setPanelVisible(false);
- mStatusBarWindowController.setForceStatusBarVisible(false);
-
- // Close any guts that might be visible
- mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
-
- mShadeController.runPostCollapseRunnables();
+ private void onExpandedInvisible() {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
showBouncerOrLockScreenIfKeyguard();
} else if (DEBUG) {
Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
}
- mCommandQueue.recomputeDisableFlags(
- mDisplayId,
- mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
-
- // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
- // the bouncer appear animation.
- if (!mKeyguardStateController.isShowing()) {
- WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- }
}
/** Called when a touch event occurred on {@link PhoneStatusBarView}. */
@@ -2072,7 +2026,8 @@
final boolean upOrCancel =
event.getAction() == MotionEvent.ACTION_UP ||
event.getAction() == MotionEvent.ACTION_CANCEL;
- setInteracting(StatusBarManager.WINDOW_STATUS_BAR, !upOrCancel || mExpandedVisible);
+ setInteracting(StatusBarManager.WINDOW_STATUS_BAR,
+ !upOrCancel || mShadeController.isExpandedVisible());
}
}
@@ -2221,7 +2176,7 @@
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
synchronized (mQueueLock) {
pw.println("Current Status Bar state:");
- pw.println(" mExpandedVisible=" + mExpandedVisible);
+ pw.println(" mExpandedVisible=" + mShadeController.isExpandedVisible());
pw.println(" mDisplayMetrics=" + mDisplayMetrics);
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller));
pw.println(" mStackScroller: " + CentralSurfaces.viewInfo(mStackScroller)
@@ -2536,10 +2491,8 @@
}
}
if (dismissShade) {
- if (mExpandedVisible && !mBouncerShowing) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */, true /* delayed*/);
+ if (mShadeController.isExpandedVisible() && !mBouncerShowing) {
+ mShadeController.animateCollapseShadeDelayed();
} else {
// Do it after DismissAction has been processed to conserve the needed
// ordering.
@@ -2581,7 +2534,7 @@
flags |= CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL;
}
}
- mShadeController.animateCollapsePanels(flags);
+ mShadeController.animateCollapseShade(flags);
}
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
if (mNotificationShadeWindowController != null) {
@@ -2696,10 +2649,9 @@
com.android.systemui.R.dimen.physical_power_button_center_screen_location_y));
}
- // Visibility reporting
protected void handleVisibleToUserChanged(boolean visibleToUser) {
if (visibleToUser) {
- handleVisibleToUserChangedImpl(visibleToUser);
+ onVisibleToUser();
mNotificationLogger.startNotificationLogging();
if (!mIsBackCallbackRegistered) {
@@ -2716,7 +2668,7 @@
}
} else {
mNotificationLogger.stopNotificationLogging();
- handleVisibleToUserChangedImpl(visibleToUser);
+ onInvisibleToUser();
if (mIsBackCallbackRegistered) {
ViewRootImpl viewRootImpl = getViewRootImpl();
@@ -2736,41 +2688,38 @@
}
}
- // Visibility reporting
- void handleVisibleToUserChangedImpl(boolean visibleToUser) {
- if (visibleToUser) {
- /* The LEDs are turned off when the notification panel is shown, even just a little bit.
- * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
- * this.
- */
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !mPresenter.isPresenterFullyCollapsed() &&
- (mState == StatusBarState.SHADE
- || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationsController.getActiveNotificationsCount();
- if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- final int finalNotificationLoad = notificationLoad;
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelRevealed(clearNotificationEffects,
- finalNotificationLoad);
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
- } else {
- mUiBgExecutor.execute(() -> {
- try {
- mBarService.onPanelHidden();
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
- }
- });
+ void onVisibleToUser() {
+ /* The LEDs are turned off when the notification panel is shown, even just a little bit.
+ * See also CentralSurfaces.setPanelExpanded for another place where we attempt to do
+ * this.
+ */
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !mPresenter.isPresenterFullyCollapsed() && (mState == StatusBarState.SHADE
+ || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mNotificationsController.getActiveNotificationsCount();
+ if (pinnedHeadsUp && mPresenter.isPresenterFullyCollapsed()) {
+ notificationLoad = 1;
}
+ final int finalNotificationLoad = notificationLoad;
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ finalNotificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
+ }
+ void onInvisibleToUser() {
+ mUiBgExecutor.execute(() -> {
+ try {
+ mBarService.onPanelHidden();
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
}
private void logStateToEventlog() {
@@ -2948,7 +2897,7 @@
private void updatePanelExpansionForKeyguard() {
if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
!= BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
@@ -3067,7 +3016,7 @@
// too heavy for the CPU and GPU on any device.
mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
} else if (!mNotificationPanelViewController.isCollapsing()) {
- instantCollapseNotificationPanel();
+ mShadeController.instantCollapseShade();
}
// Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
@@ -3225,8 +3174,7 @@
@Override
public boolean onMenuPressed() {
if (shouldUnlockOnMenuPressed()) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3271,7 +3219,7 @@
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
&& !isBouncerShowingOverDream()) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
return true;
}
@@ -3281,8 +3229,7 @@
@Override
public boolean onSpacePressed() {
if (mDeviceInteractive && mState != StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL /* flags */, true /* force */);
+ mShadeController.animateCollapseShadeForced();
return true;
}
return false;
@@ -3322,12 +3269,6 @@
}
}
- @Override
- public void instantCollapseNotificationPanel() {
- mNotificationPanelViewController.instantCollapse();
- mShadeController.runPostCollapseRunnables();
- }
-
/**
* Collapse the panel directly if we are on the main thread, post the collapsing on the main
* thread if we are not.
@@ -3335,9 +3276,9 @@
@Override
public void collapsePanelOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+ mContext.getMainExecutor().execute(mShadeController::collapseShade);
}
}
@@ -3477,7 +3418,7 @@
mNotificationShadeWindowViewController.cancelCurrentTouch();
}
if (mPanelExpanded && mState == StatusBarState.SHADE) {
- mShadeController.animateCollapsePanels();
+ mShadeController.animateCollapseShade();
}
}
@@ -3540,7 +3481,7 @@
// The unlocked screen off and fold to aod animations might use our LightRevealScrim -
// we need to be expanded for it to be visible.
if (mDozeParameters.shouldShowLightRevealScrim()) {
- makeExpandedVisible(true);
+ mShadeController.makeExpandedVisible(true);
}
DejankUtils.stopDetectingBlockingIpcs(tag);
@@ -3569,7 +3510,7 @@
// If we are waking up during the screen off animation, we should undo making the
// expanded visible (we did that so the LightRevealScrim would be visible).
if (mScreenOffAnimationController.shouldHideLightRevealScrimOnWakeUp()) {
- makeExpandedInvisible();
+ mShadeController.makeExpandedInvisible();
}
});
@@ -3624,6 +3565,12 @@
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
+ //TODO(b/257041702) delete
+ @Override
+ public void makeExpandedVisible(boolean force) {
+ mShadeController.makeExpandedVisible(force);
+ }
+
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurningOn(Runnable onDrawn) {
@@ -3904,8 +3851,7 @@
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
if (BANNER_ACTION_SETUP.equals(action)) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
- true /* force */);
+ mShadeController.animateCollapseShadeForced();
mContext.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_REDACTION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -3967,7 +3913,7 @@
action.run();
}).start();
- return collapsePanel ? mShadeController.collapsePanel() : willAnimateOnKeyguard;
+ return collapsePanel ? mShadeController.collapseShade() : willAnimateOnKeyguard;
}
@Override
@@ -4062,8 +4008,7 @@
mMainExecutor.execute(runnable);
}
- @Override
- public void visibilityChanged(boolean visible) {
+ private void onShadeVisibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
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 44ad604..f9d316b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -469,6 +469,9 @@
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
+ } else if (mKeyguardStateController.isOccluded()
+ && !mDreamOverlayStateController.isOverlayActive()) {
+ return;
} else if (needsFullscreenBouncer()) {
if (mPrimaryBouncer != null) {
mPrimaryBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index b6ae4a0..05bf860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -260,11 +260,11 @@
if (showOverLockscreen) {
mShadeController.addPostCollapseAction(runnable);
- mShadeController.collapsePanel(true /* animate */);
+ mShadeController.collapseShade(true /* animate */);
} else if (mKeyguardStateController.isShowing()
&& mCentralSurfaces.isOccluded()) {
mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
runnable.run();
}
@@ -406,7 +406,7 @@
private void expandBubbleStack(NotificationEntry entry) {
mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
}
private void startNotificationIntent(
@@ -593,9 +593,9 @@
private void collapseOnMainThread() {
if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapsePanel();
+ mShadeController.collapseShade();
} else {
- mMainThreadHandler.post(mShadeController::collapsePanel);
+ mMainThreadHandler.post(mShadeController::collapseShade);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8a49850..7fe01825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -180,7 +180,7 @@
}
};
mShadeController.postOnShadeExpanded(clickPendingViewRunnable);
- mShadeController.instantExpandNotificationsPanel();
+ mShadeController.instantExpandShade();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index c5b697c..4c59874 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -493,7 +493,7 @@
fun showUserSwitcher(context: Context, expandable: Expandable) {
if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
- showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 85c2964..14cc3e7 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -18,11 +18,13 @@
package com.android.systemui.user.domain.model
import android.os.UserHandle
+import com.android.systemui.animation.Expandable
import com.android.systemui.qs.user.UserSwitchDialogController
/** Encapsulates a request to show a dialog. */
sealed class ShowDialogRequestModel(
open val dialogShower: UserSwitchDialogController.DialogShower? = null,
+ open val expandable: Expandable? = null,
) {
data class ShowAddUserDialog(
val userHandle: UserHandle,
@@ -45,5 +47,7 @@
) : ShowDialogRequestModel(dialogShower)
/** Show the user switcher dialog */
- object ShowUserSwitcherDialog : ShowDialogRequestModel()
+ data class ShowUserSwitcherDialog(
+ override val expandable: Expandable?,
+ ) : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
new file mode 100644
index 0000000..3fe2a7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/DialogShowerImpl.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.user.ui.dialog
+
+import android.app.Dialog
+import android.content.DialogInterface
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.qs.user.UserSwitchDialogController.DialogShower
+
+/** Extracted from [UserSwitchDialogController] */
+class DialogShowerImpl(
+ private val animateFrom: Dialog,
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+) : DialogInterface by animateFrom, DialogShower {
+ override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
+ dialogLaunchAnimator.showFromDialog(dialog, animateFrom = animateFrom, cuj)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
index ed25898..b8ae257 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitchDialog.kt
@@ -60,6 +60,7 @@
setView(gridFrame)
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
+ adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 4141054..d451230 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -133,7 +133,10 @@
}
currentDialog = dialog
- if (request.dialogShower != null && dialogCuj != null) {
+ val controller = request.expandable?.dialogLaunchController(dialogCuj)
+ if (controller != null) {
+ dialogLaunchAnimator.get().show(dialog, controller)
+ } else if (request.dialogShower != null && dialogCuj != null) {
request.dialogShower?.showDialog(dialog, dialogCuj)
} else {
dialog.show()
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index ad97ef4..5df4a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -29,6 +29,7 @@
import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
@@ -102,6 +103,15 @@
}
@Override
+ public Looper onProvideEngineLooper() {
+ // Receive messages on mWorker thread instead of SystemUI's main handler.
+ // All other wallpapers have their own process, and they can receive messages on their own
+ // main handler without any delay. But since ImageWallpaper lives in SystemUI, performance
+ // of the image wallpaper could be negatively affected when SystemUI's main handler is busy.
+ return mWorker != null ? mWorker.getLooper() : super.onProvideEngineLooper();
+ }
+
+ @Override
public void onCreate() {
super.onCreate();
mWorker = new HandlerThread(TAG);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index a4384d5..7033ccd 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -549,7 +549,7 @@
} catch (RemoteException e) {
Log.e(TAG, e.getMessage());
}
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
@@ -597,7 +597,7 @@
}
if (shouldBubble) {
- mShadeController.collapsePanel(true);
+ mShadeController.collapseShade(true);
if (entry.getRow() != null) {
entry.getRow().updateBubbleButton();
}
diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties
new file mode 100644
index 0000000..2a75bd9
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
new file mode 100644
index 0000000..188dff2
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiResourceLoadingTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.robotests;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import static com.google.common.truth.Truth.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SysuiResourceLoadingTest extends SysuiRoboBase {
+ @Test
+ public void testResources() {
+ assertThat(getContext().getString(com.android.systemui.R.string.app_label))
+ .isEqualTo("System UI");
+ assertThat(getContext().getString(com.android.systemui.tests.R.string.test_content))
+ .isNotEmpty();
+ }
+}
diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
new file mode 100644
index 0000000..d9686bb
--- /dev/null
+++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/robotests/SysuiRoboBase.java
@@ -0,0 +1,27 @@
+/*
+ * 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.robotests;
+
+import android.content.Context;
+
+import androidx.test.InstrumentationRegistry;
+
+public class SysuiRoboBase {
+ public Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cedde58..32c5b3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -36,8 +36,8 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.mock
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
index 623becf..7205f30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/CameraQuickAffordanceConfigTest.kt
@@ -37,25 +37,29 @@
@Mock private lateinit var cameraGestureHelper: CameraGestureHelper
@Mock private lateinit var context: Context
+
private lateinit var underTest: CameraQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- underTest = CameraQuickAffordanceConfig(
+
+ underTest =
+ CameraQuickAffordanceConfig(
context,
- cameraGestureHelper,
- )
+ ) {
+ cameraGestureHelper
+ }
}
@Test
fun `affordance triggered -- camera launch called`() {
- //when
+ // When
val result = underTest.onTriggered(null)
- //then
+ // Then
verify(cameraGestureHelper)
- .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
+ .launchCamera(StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE)
assertEquals(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled, result)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 8ef921e..552b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d8ee9f1..6a2376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
@@ -27,10 +28,15 @@
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,8 +44,12 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
@@ -60,15 +70,23 @@
sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
}
userTracker = FakeUserTracker()
+ val dispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(dispatcher)
underTest =
KeyguardQuickAffordanceSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
}
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun setSelections() = runTest {
overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
@@ -318,6 +336,22 @@
job.cancel()
}
+ @Test
+ fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ clearInvocations(userFileManager)
+
+ fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+ verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+ job.cancel()
+ }
+
private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5c75417..652fae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -76,6 +76,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
underTest =
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 c2650ec..ba7c40b 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
@@ -252,6 +252,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b790306..8d0c4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -113,6 +113,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
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 8b166bd..32849cd 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
@@ -136,6 +136,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
lateinit var userFileManager: UserFileManagerImpl
lateinit var backgroundExecutor: FakeExecutor
- @Mock
- lateinit var userManager: UserManager
- @Mock
- lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
backgroundExecutor = FakeExecutor(FakeSystemClock())
- userFileManager = UserFileManagerImpl(context, userManager,
- broadcastDispatcher, backgroundExecutor)
+ userFileManager =
+ UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
}
@After
fun end() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID)
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@@ -82,13 +78,14 @@
@Test
fun testGetSharedPreferences() {
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- UserFileManagerImpl.SHARED_PREFS,
- TEST_FILE_NAME
- )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
assertThat(secondarySharedPref).isNotNull()
assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
val userFileManager = spy(userFileManager)
userFileManager.start()
verify(userFileManager).clearDeletedUserData()
- verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
- any(IntentFilter::class.java),
- any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java),
+ isNull(),
+ eq(Context.RECEIVER_EXPORTED),
+ isNull()
+ )
}
@Test
fun testClearDeletedUserData() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files"
- )
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
dir.mkdirs()
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
@Test
fun testEnsureParentDirExists() {
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
assertThat(file.parentFile.exists()).isFalse()
- userFileManager.ensureParentDirExists(file)
+ UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index e1346ea..0000c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -118,13 +118,6 @@
}
@Test
- public void testCollapsePanels() {
- mCommandQueue.animateCollapsePanels();
- waitForIdleSync();
- verify(mCallbacks).animateCollapsePanels(eq(0), eq(false));
- }
-
- @Test
public void testExpandSettings() {
String panel = "some_panel";
mCommandQueue.animateExpandSettingsPanel(panel);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index ed2afe7..915924f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -41,7 +41,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.notification.logging.NotificationPanelLogger;
-import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerFake;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index d5bfe1f..c17c5b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -136,7 +136,7 @@
StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
// Trying to open it does nothing.
mSbcqCallbacks.animateExpandNotificationsPanel();
@@ -154,7 +154,7 @@
mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
StatusBarManager.DISABLE2_NONE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
- verify(mShadeController, never()).animateCollapsePanels();
+ verify(mShadeController, never()).animateCollapseShade();
// Can now be opened.
mSbcqCallbacks.animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 013e727..ed84e42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -392,10 +392,21 @@
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
- mShadeController = spy(new ShadeControllerImpl(mCommandQueue,
- mStatusBarStateController, mNotificationShadeWindowController,
- mStatusBarKeyguardViewManager, mContext.getSystemService(WindowManager.class),
- () -> Optional.of(mCentralSurfaces), () -> mAssistManager));
+ mShadeController = spy(new ShadeControllerImpl(
+ mCommandQueue,
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mStatusBarKeyguardViewManager,
+ mStatusBarWindowController,
+ mNotificationShadeWindowController,
+ mContext.getSystemService(WindowManager.class),
+ () -> mAssistManager,
+ () -> mNotificationGutsManager
+ ));
+ mShadeController.setNotificationPanelViewController(mNotificationPanelViewController);
+ mShadeController.setNotificationShadeWindowViewController(
+ mNotificationShadeWindowViewController);
+ mShadeController.setNotificationPresenter(mNotificationPresenter);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
@@ -492,6 +503,7 @@
return mViewRootImpl;
}
};
+ mCentralSurfaces.initShadeVisibilityListener();
when(mViewRootImpl.getOnBackInvokedDispatcher())
.thenReturn(mOnBackInvokedDispatcher);
when(mKeyguardViewMediator.registerCentralSurfaces(
@@ -807,7 +819,7 @@
when(mNotificationPanelViewController.canPanelBeCollapsed()).thenReturn(true);
mOnBackInvokedCallback.getValue().onBackInvoked();
- verify(mShadeController).animateCollapsePanels();
+ verify(mShadeController).animateCollapseShade();
}
@Test
@@ -1030,7 +1042,7 @@
}
@Test
- public void collapseShade_callsAnimateCollapsePanels_whenExpanded() {
+ public void collapseShade_callsanimateCollapseShade_whenExpanded() {
// GIVEN the shade is expanded
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1038,12 +1050,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShade_doesNotCallAnimateCollapsePanels_whenCollapsed() {
+ public void collapseShade_doesNotCallanimateCollapseShade_whenCollapsed() {
// GIVEN the shade is collapsed
mCentralSurfaces.onShadeExpansionFullyChanged(false);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1051,12 +1063,12 @@
// WHEN collapseShade is called
mCentralSurfaces.collapseShade();
- // VERIFY that animateCollapsePanels is NOT called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is NOT called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_callsAnimateCollapsePanels_whenFlagDisabled() {
+ public void collapseShadeForBugReport_callsanimateCollapseShade_whenFlagDisabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1065,12 +1077,12 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController).animateCollapseShade();
}
@Test
- public void collapseShadeForBugReport_doesNotCallAnimateCollapsePanels_whenFlagEnabled() {
+ public void collapseShadeForBugReport_doesNotCallanimateCollapseShade_whenFlagEnabled() {
// GIVEN the shade is expanded & flag enabled
mCentralSurfaces.onShadeExpansionFullyChanged(true);
mCentralSurfaces.setBarStateForTest(StatusBarState.SHADE);
@@ -1079,8 +1091,8 @@
// WHEN collapseShadeForBugreport is called
mCentralSurfaces.collapseShadeForBugreport();
- // VERIFY that animateCollapsePanels is called
- verify(mShadeController, never()).animateCollapsePanels();
+ // VERIFY that animateCollapseShade is called
+ verify(mShadeController, never()).animateCollapseShade();
}
@Test
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 bf5186b..e467d93 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
@@ -307,6 +307,17 @@
}
@Test
+ public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+ expansionEvent(
+ /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+ /* expanded= */ true,
+ /* tracking= */ false));
+ verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+ }
+
+ @Test
public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
// Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
// the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ce54d78..cae414a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -263,7 +263,7 @@
while (!runnables.isEmpty()) runnables.remove(0).run();
// Then
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(any(),
eq(false) /* animate */, any(), any());
@@ -296,7 +296,7 @@
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
// This is called regardless, and simply short circuits when there is nothing to do.
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -329,7 +329,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
@@ -357,7 +357,7 @@
// Then
verify(mBubblesManager).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
- verify(mShadeController, atLeastOnce()).collapsePanel();
+ verify(mShadeController, atLeastOnce()).collapseShade();
verify(mAssistManager).hideAssist();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 50d239d..ec555c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -813,7 +813,8 @@
val job = underTest.dialogShowRequests.onEach { dialogRequest = it }.launchIn(this)
// Dialog is shown.
- assertThat(dialogRequest).isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog)
+ assertThat(dialogRequest)
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
underTest.onDialogShown()
assertThat(dialogRequest).isNull()
diff --git a/services/api/current.txt b/services/api/current.txt
index 42ae10e..834ed2f 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -76,6 +76,7 @@
method @Nullable public String getSdkLibraryName();
method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
method @Nullable public String getStaticSharedLibraryName();
+ method @NonNull public java.util.UUID getStorageUuid();
method public int getTargetSdkVersion();
method public boolean isDebuggable();
method public boolean isIsolatedSplitLoading();
@@ -99,6 +100,7 @@
method public int getAppId();
method @NonNull public String getPackageName();
method @Nullable public String getPrimaryCpuAbi();
+ method @Nullable public String getSeInfo();
method @Nullable public String getSecondaryCpuAbi();
method @NonNull public com.android.server.pm.pkg.PackageUserState getStateForUser(@NonNull android.os.UserHandle);
method @NonNull public java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries();
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 0000000..ec7e993
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * 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.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+ private static final String TAG = "SensorController";
+
+ private final Object mLock;
+ private final int mVirtualDeviceId;
+ @GuardedBy("mLock")
+ private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+ private final SensorManagerInternal mSensorManagerInternal;
+
+ public SensorController(@NonNull Object lock, int virtualDeviceId) {
+ mLock = lock;
+ mVirtualDeviceId = virtualDeviceId;
+ mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+ }
+
+ void close() {
+ synchronized (mLock) {
+ final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+ mSensorDescriptors.entrySet().iterator();
+ if (iterator.hasNext()) {
+ final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+ final IBinder token = entry.getKey();
+ final SensorDescriptor sensorDescriptor = entry.getValue();
+ iterator.remove();
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+ }
+
+ void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(deviceToken);
+ Objects.requireNonNull(config);
+ try {
+ createSensorInternal(deviceToken, config);
+ } catch (SensorCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual sensor '" + config.getName() + "'.", e);
+ }
+ }
+
+ private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+ throws SensorCreationException {
+ final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+ (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+ IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+ if (callback != null) {
+ try {
+ callback.onStateChanged(
+ enabled, samplingPeriodMicros, batchReportLatencyMicros);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to call sensor callback.", e);
+ }
+ }
+ };
+
+ final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+ config.getType(), config.getName(),
+ config.getVendor() == null ? "" : config.getVendor(),
+ runtimeSensorCallback);
+ if (handle <= 0) {
+ throw new SensorCreationException("Received an invalid virtual sensor handle.");
+ }
+
+ // The handle is valid from here, so ensure that all failures clean it up.
+ final BinderDeathRecipient binderDeathRecipient;
+ try {
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ throw new SensorCreationException("Client died before sensor could be created.", e);
+ }
+
+ synchronized (mLock) {
+ SensorDescriptor sensorDescriptor = new SensorDescriptor(
+ handle, config.getType(), config.getName(), binderDeathRecipient);
+ mSensorDescriptors.put(deviceToken, sensorDescriptor);
+ }
+ }
+
+ boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(event);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not send sensor event for given token");
+ }
+ return mSensorManagerInternal.sendSensorEvent(
+ sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+ event.getTimestampNanos(), event.getValues());
+ }
+ }
+
+ void unregisterSensor(@NonNull IBinder token) {
+ Objects.requireNonNull(token);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not unregister sensor for given token");
+ }
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+ token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+ final int handle = sensorDescriptor.getHandle();
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ }
+
+
+ void dump(@NonNull PrintWriter fout) {
+ fout.println(" SensorController: ");
+ synchronized (mLock) {
+ fout.println(" Active descriptors: ");
+ for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+ fout.println(" handle: " + sensorDescriptor.getHandle());
+ fout.println(" type: " + sensorDescriptor.getType());
+ fout.println(" name: " + sensorDescriptor.getName());
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+ synchronized (mLock) {
+ mSensorDescriptors.put(deviceToken,
+ new SensorDescriptor(handle, type, name, () -> {}));
+ }
+ }
+
+ @VisibleForTesting
+ Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+ synchronized (mLock) {
+ return mSensorDescriptors;
+ }
+ }
+
+ @VisibleForTesting
+ static final class SensorDescriptor {
+
+ private final int mHandle;
+ private final IBinder.DeathRecipient mDeathRecipient;
+ private final int mType;
+ private final String mName;
+
+ SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+ mHandle = handle;
+ mDeathRecipient = deathRecipient;
+ mType = type;
+ mName = name;
+ }
+ public int getHandle() {
+ return mHandle;
+ }
+ public int getType() {
+ return mType;
+ }
+ public String getName() {
+ return mName;
+ }
+ public IBinder.DeathRecipient getDeathRecipient() {
+ return mDeathRecipient;
+ }
+ }
+
+ private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+ private final IBinder mDeviceToken;
+
+ BinderDeathRecipient(IBinder deviceToken) {
+ mDeviceToken = deviceToken;
+ }
+
+ @Override
+ public void binderDied() {
+ // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+ // quitting, which removes this death recipient. If this is invoked, the remote end
+ // died, or they disposed of the object without properly unregistering.
+ Slog.e(TAG, "Virtual sensor controller binder died");
+ unregisterSensor(mDeviceToken);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+ private static class SensorCreationException extends Exception {
+ SensorCreationException(String message) {
+ super(message);
+ }
+ SensorCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+}
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 7e82918..828f302 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -38,6 +38,8 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -76,6 +78,7 @@
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -98,6 +101,7 @@
private final int mOwnerUid;
private final int mDeviceId;
private final InputController mInputController;
+ private final SensorController mSensorController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -160,6 +164,7 @@
ownerUid,
deviceId,
/* inputController= */ null,
+ /* sensorController= */ null,
listener,
pendingTrampolineCallback,
activityListener,
@@ -175,6 +180,7 @@
int ownerUid,
int deviceId,
InputController inputController,
+ SensorController sensorController,
OnDeviceCloseListener listener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -198,6 +204,11 @@
} else {
mInputController = inputController;
}
+ if (sensorController == null) {
+ mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+ } else {
+ mSensorController = sensorController;
+ }
mListener = listener;
try {
token.linkToDeath(this, 0);
@@ -319,11 +330,12 @@
mListener.onClose(mAssociationInfo.getId());
mAppToken.unlinkToDeath(this, 0);
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.close();
+ mSensorController.close();
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -403,12 +415,12 @@
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -430,12 +442,12 @@
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -457,11 +469,11 @@
+ "virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -491,12 +503,12 @@
+ screenSize);
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createTouchscreen(deviceName, vendorId, productId,
deviceToken, displayId, screenSize);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -506,92 +518,92 @@
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.unregisterInputDevice(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public int getInputDeviceId(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getInputDeviceId(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendDpadKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendButtonEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendTouchEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRelativeEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendScrollEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public PointF getCursorPosition(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getCursorPosition(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -601,7 +613,7 @@
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
@@ -610,7 +622,50 @@
}
}
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void createVirtualSensor(
+ @NonNull IBinder deviceToken,
+ @NonNull VirtualSensorConfig config) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual sensor");
+ Objects.requireNonNull(config);
+ Objects.requireNonNull(deviceToken);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.createSensor(deviceToken, config);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public void unregisterSensor(@NonNull IBinder token) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to unregister a virtual sensor");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.unregisterSensor(token);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ @Override // Binder call
+ public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to send a virtual sensor event");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mSensorController.sendSensorEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -627,6 +682,7 @@
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
+ mSensorController.dump(fout);
}
GenericWindowPolicyController createWindowPolicyController(
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index fe26700..2b62f69 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -32,6 +32,7 @@
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
+import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -280,7 +281,22 @@
@Override
public void onClose(int associationId) {
synchronized (mVirtualDeviceManagerLock) {
- mVirtualDevices.remove(associationId);
+ VirtualDeviceImpl removedDevice =
+ mVirtualDevices.removeReturnOld(associationId);
+ if (removedDevice != null) {
+ Intent i = new Intent(
+ VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
+ i.putExtra(
+ VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
+ removedDevice.getDeviceId());
+ i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ getContext().sendBroadcastAsUser(i, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
mAppsOnVirtualDevices.remove(associationId);
if (cameraAccessController != null) {
cameraAccessController.stopObservingIfNeeded();
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 68880bd..6cd7ce8 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -310,27 +310,27 @@
Bundle packageMeasurement = measurePackage(packageInfo);
results.add(packageMeasurement);
- if (record) {
+ if (record && (mba_status == MBA_STATUS_UPDATED_PRELOAD)) {
// compute digests of signing info
String[] signerDigestHexStrings = computePackageSignerSha256Digests(
packageInfo.signingInfo);
// now we should have all the bits for the atom
- /* TODO: Uncomment and test after merging new atom definition.
+ byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
- HexEncoding.encodeToString(packageMeasurement.getByteArray(
- BUNDLE_CONTENT_DIGEST), false),
+ (cDigest != null) ? HexEncoding.encodeToString(
+ packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+ false) : null,
packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
signerDigestHexStrings, // signer_cert_digest
- mba_status, // mba_status
+ mba_status, // mba_status
null, // initiator
null, // initiator_signer_digest
null, // installer
null // originator
);
- */
}
}
if (DEBUG) {
@@ -377,12 +377,13 @@
}
// we should now have all the info needed for the atom
- /* TODO: Uncomment and test after merging new atom definition.
+ byte[] cDigest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
FrameworkStatsLog.write(FrameworkStatsLog.MOBILE_BUNDLED_APP_INFO_GATHERED,
packageInfo.packageName,
packageInfo.getLongVersionCode(),
- HexEncoding.encodeToString(packageMeasurement.getByteArray(
- BUNDLE_CONTENT_DIGEST), false),
+ (cDigest != null) ? HexEncoding.encodeToString(
+ packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST),
+ false) : null,
packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM),
signerDigestHexStrings,
MBA_STATUS_NEW_INSTALL, // mba_status
@@ -391,7 +392,6 @@
installer,
originator
);
- */
}
}
if (DEBUG) {
@@ -489,24 +489,10 @@
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
- // TODO(b/259348134): consider refactoring the following to a helper method
- switch (algorithmId) {
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
- pw.print("CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
- pw.print("CHUNKED_SHA512:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
- pw.print("VERITY_CHUNKED_SHA256:");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
- pw.print("SHA256:");
- break;
- default:
- pw.print("UNKNOWN_ALGO_ID(" + algorithmId + "):");
- }
+ pw.print(translateContentDigestAlgorithmIdToString(algorithmId));
+ pw.print(":");
pw.print(HexEncoding.encodeToString(contentDigest, false));
+ pw.print("\n");
}
}
@@ -533,31 +519,13 @@
pw.println("ERROR: Failed to compute package content digest for "
+ origPackageFilepath);
} else {
- // TODO(b/259348134): consider refactoring this to a helper method
for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
Integer algorithmId = entry.getKey();
byte[] contentDigest = entry.getValue();
- pw.print("|--> Pre-installed package content digest algorithm: ");
- switch (algorithmId) {
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
- pw.print("CHUNKED_SHA256");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
- pw.print("CHUNKED_SHA512");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
- pw.print("VERITY_CHUNKED_SHA256");
- break;
- case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
- pw.print("SHA256");
- break;
- default:
- pw.print("UNKNOWN");
- }
- pw.print("\n");
- pw.print("|--> Pre-installed package content digest: ");
- pw.print(HexEncoding.encodeToString(contentDigest, false));
- pw.print("\n");
+ pw.println("|--> Pre-installed package content digest: "
+ + HexEncoding.encodeToString(contentDigest, false));
+ pw.println("|--> Pre-installed package content digest algorithm: "
+ + translateContentDigestAlgorithmIdToString(algorithmId));
}
}
}
@@ -739,7 +707,6 @@
pw.print(packageName + ","
+ packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, pw);
- pw.print("\n");
if (verbose) {
ModuleInfo moduleInfo;
@@ -802,7 +769,6 @@
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, pw);
- pw.print("\n");
if (verbose) {
printModuleDetails(module, pw);
@@ -858,7 +824,6 @@
pw.print(packageInfo.packageName + ",");
pw.print(packageInfo.getLongVersionCode() + ",");
printPackageMeasurements(packageInfo, pw);
- pw.print("\n");
if (verbose) {
printAppDetails(packageInfo, printLibraries, pw);
@@ -1079,6 +1044,21 @@
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
}
+ private String translateContentDigestAlgorithmIdToString(int algorithmId) {
+ switch (algorithmId) {
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+ return "CHUNKED_SHA256";
+ case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+ return "CHUNKED_SHA512";
+ case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+ return "VERITY_CHUNKED_SHA256";
+ case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+ return "SHA256";
+ default:
+ return "UNKNOWN_ALGO_ID(" + algorithmId + ")";
+ }
+ }
+
@NonNull
private List<PackageInfo> getCurrentInstalledApexs() {
List<PackageInfo> results = new ArrayList<>();
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 540ed4c..3487613 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -19,6 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -73,6 +74,7 @@
private final boolean mAllowTheaterModeWakeFromDock;
private final List<ExtconStateConfig> mExtconStateConfigs;
+ private DeviceProvisionedObserver mDeviceProvisionedObserver;
static final class ExtconStateProvider {
private final Map<String, String> mState;
@@ -110,7 +112,7 @@
Slog.w(TAG, "No state file found at: " + stateFilePath);
return new ExtconStateProvider(new HashMap<>());
} catch (Exception e) {
- Slog.e(TAG, "" , e);
+ Slog.e(TAG, "", e);
return new ExtconStateProvider(new HashMap<>());
}
}
@@ -136,7 +138,7 @@
private static List<ExtconStateConfig> loadExtconStateConfigs(Context context) {
String[] rows = context.getResources().getStringArray(
- com.android.internal.R.array.config_dockExtconStateMapping);
+ com.android.internal.R.array.config_dockExtconStateMapping);
try {
ArrayList<ExtconStateConfig> configs = new ArrayList<>();
for (String row : rows) {
@@ -167,6 +169,7 @@
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
mKeepDreamingWhenUndocking = context.getResources().getBoolean(
com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -199,15 +202,19 @@
if (phase == PHASE_ACTIVITY_MANAGER_READY) {
synchronized (mLock) {
mSystemReady = true;
-
- // don't bother broadcasting undocked here
- if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
- updateLocked();
- }
+ mDeviceProvisionedObserver.onSystemReady();
+ updateIfDockedLocked();
}
}
}
+ private void updateIfDockedLocked() {
+ // don't bother broadcasting undocked here
+ if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+ updateLocked();
+ }
+ }
+
private void setActualDockStateLocked(int newState) {
mActualDockState = newState;
if (!mUpdatesStopped) {
@@ -252,8 +259,7 @@
// Skip the dock intent if not yet provisioned.
final ContentResolver cr = getContext().getContentResolver();
- if (Settings.Global.getInt(cr,
- Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+ if (!mDeviceProvisionedObserver.isDeviceProvisioned()) {
Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
return;
}
@@ -419,4 +425,48 @@
}
}
}
+
+ private final class DeviceProvisionedObserver extends ContentObserver {
+ private boolean mRegistered;
+
+ public DeviceProvisionedObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mLock) {
+ updateRegistration();
+ if (isDeviceProvisioned()) {
+ // Send the dock broadcast if device is docked after provisioning.
+ updateIfDockedLocked();
+ }
+ }
+ }
+
+ void onSystemReady() {
+ updateRegistration();
+ }
+
+ private void updateRegistration() {
+ boolean register = !isDeviceProvisioned();
+ if (register == mRegistered) {
+ return;
+ }
+ final ContentResolver resolver = getContext().getContentResolver();
+ if (register) {
+ resolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
+ false, this);
+ } else {
+ resolver.unregisterContentObserver(this);
+ }
+ mRegistered = register;
+ }
+
+ boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(getContext().getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index b56d1fc..b4ab254 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -447,6 +447,13 @@
thread.start();
break;
case LEVEL_FACTORY_RESET:
+ // Before the completion of Reboot, if any crash happens then PackageWatchdog
+ // escalates to next level i.e. factory reset, as they happen in separate threads.
+ // Adding a check to prevent factory reset to execute before above reboot completes.
+ // Note: this reboot property is not persistent resets after reboot is completed.
+ if (isRebootPropertySet()) {
+ break;
+ }
SystemProperties.set(PROP_ATTEMPTING_FACTORY_RESET, "true");
runnable = new Runnable() {
@Override
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index ca86021c..bd90d85 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -52,8 +52,8 @@
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
import android.telephony.BarringInfo;
-import android.telephony.CallAttributes;
import android.telephony.CallQuality;
+import android.telephony.CallState;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
@@ -82,6 +82,7 @@
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -349,9 +350,9 @@
private CallQuality[] mCallQuality;
- private CallAttributes[] mCallAttributes;
+ private ArrayList<List<CallState>> mCallStateLists;
- // network type of the call associated with the mCallAttributes and mCallQuality
+ // network type of the call associated with the mCallStateLists and mCallQuality
private int[] mCallNetworkType;
private int[] mSrvccState;
@@ -687,7 +688,6 @@
mCallPreciseDisconnectCause = copyOf(mCallPreciseDisconnectCause, mNumPhones);
mCallQuality = copyOf(mCallQuality, mNumPhones);
mCallNetworkType = copyOf(mCallNetworkType, mNumPhones);
- mCallAttributes = copyOf(mCallAttributes, mNumPhones);
mOutgoingCallEmergencyNumber = copyOf(mOutgoingCallEmergencyNumber, mNumPhones);
mOutgoingSmsEmergencyNumber = copyOf(mOutgoingSmsEmergencyNumber, mNumPhones);
mTelephonyDisplayInfos = copyOf(mTelephonyDisplayInfos, mNumPhones);
@@ -707,6 +707,7 @@
cutListToSize(mLinkCapacityEstimateLists, mNumPhones);
cutListToSize(mCarrierPrivilegeStates, mNumPhones);
cutListToSize(mCarrierServiceStates, mNumPhones);
+ cutListToSize(mCallStateLists, mNumPhones);
return;
}
@@ -730,8 +731,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+ mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -799,7 +799,7 @@
mCallPreciseDisconnectCause = new int[numPhones];
mCallQuality = new CallQuality[numPhones];
mCallNetworkType = new int[numPhones];
- mCallAttributes = new CallAttributes[numPhones];
+ mCallStateLists = new ArrayList<>();
mPreciseDataConnectionStates = new ArrayList<>();
mCellInfo = new ArrayList<>(numPhones);
mImsReasonInfo = new ArrayList<>();
@@ -837,8 +837,7 @@
mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
mCallQuality[i] = createCallQuality();
- mCallAttributes[i] = new CallAttributes(createPreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality());
+ mCallStateLists.add(i, new ArrayList<>());
mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mPreciseCallState[i] = createPreciseCallState();
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
@@ -1336,7 +1335,7 @@
}
if (events.contains(TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[r.phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(r.phoneId));
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -2171,11 +2170,30 @@
}
}
- public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
- int foregroundCallState, int backgroundCallState) {
+ /**
+ * Send a notification to registrants that the precise call state has changed.
+ *
+ * @param phoneId the phoneId carrying the data connection
+ * @param subId the subscriptionId for the data connection
+ * @param callStates Array of PreciseCallState of foreground, background & ringing calls.
+ * @param imsCallIds Array of IMS call session ID{@link ImsCallSession#getCallId()} for
+ * ringing, foreground & background calls.
+ * @param imsServiceTypes Array of IMS call service type for ringing, foreground &
+ * background calls.
+ * @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ */
+ public void notifyPreciseCallState(int phoneId, int subId,
+ @Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
+ @Annotation.ImsCallServiceType int[] imsServiceTypes,
+ @Annotation.ImsCallType int[] imsCallTypes) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
return;
}
+
+ int ringingCallState = callStates[CallState.CALL_CLASSIFICATION_RINGING];
+ int foregroundCallState = callStates[CallState.CALL_CLASSIFICATION_FOREGROUND];
+ int backgroundCallState = callStates[CallState.CALL_CLASSIFICATION_BACKGROUND];
+
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
mRingingCallState[phoneId] = ringingCallState;
@@ -2186,11 +2204,11 @@
backgroundCallState,
DisconnectCause.NOT_VALID,
PreciseDisconnectCause.NOT_VALID);
- boolean notifyCallAttributes = true;
+ boolean notifyCallState = true;
if (mCallQuality == null) {
log("notifyPreciseCallState: mCallQuality is null, "
+ "skipping call attributes");
- notifyCallAttributes = false;
+ notifyCallState = false;
} else {
// If the precise call state is no longer active, reset the call network type
// and call quality.
@@ -2199,8 +2217,65 @@
mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
mCallQuality[phoneId] = createCallQuality();
}
- mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
- mCallNetworkType[phoneId], mCallQuality[phoneId]);
+ mCallStateLists.get(phoneId).clear();
+ if (foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && foregroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallQuality callQuality = mCallQuality[phoneId];
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(callQuality)
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_FOREGROUND);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_FOREGROUND])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_FOREGROUND]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
+ if (backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && backgroundCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(createCallQuality())
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_BACKGROUND);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_BACKGROUND])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_BACKGROUND]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
+ if (ringingCallState != PreciseCallState.PRECISE_CALL_STATE_NOT_VALID
+ && ringingCallState != PreciseCallState.PRECISE_CALL_STATE_IDLE) {
+ CallState.Builder builder = new CallState.Builder(
+ callStates[CallState.CALL_CLASSIFICATION_RINGING])
+ .setNetworkType(mCallNetworkType[phoneId])
+ .setCallQuality(createCallQuality())
+ .setCallClassification(
+ CallState.CALL_CLASSIFICATION_RINGING);
+ if (imsCallIds != null && imsServiceTypes != null && imsCallTypes != null) {
+ builder = builder
+ .setImsCallSessionId(imsCallIds[
+ CallState.CALL_CLASSIFICATION_RINGING])
+ .setImsCallServiceType(imsServiceTypes[
+ CallState.CALL_CLASSIFICATION_RINGING])
+ .setImsCallType(imsCallTypes[
+ CallState.CALL_CLASSIFICATION_RINGING]);
+ }
+ mCallStateLists.get(phoneId).add(builder.build());
+ }
}
for (Record r : mRecords) {
@@ -2213,11 +2288,11 @@
mRemoveList.add(r.binder);
}
}
- if (notifyCallAttributes && r.matchTelephonyCallbackEvent(
+ if (notifyCallState && r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2515,15 +2590,29 @@
// merge CallQuality with PreciseCallState and network type
mCallQuality[phoneId] = callQuality;
mCallNetworkType[phoneId] = callNetworkType;
- mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
- callNetworkType, callQuality);
+ if (mCallStateLists.get(phoneId).size() > 0
+ && mCallStateLists.get(phoneId).get(0).getCallState()
+ == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ CallState prev = mCallStateLists.get(phoneId).remove(0);
+ mCallStateLists.get(phoneId).add(
+ 0, new CallState.Builder(prev.getCallState())
+ .setNetworkType(callNetworkType)
+ .setCallQuality(callQuality)
+ .setCallClassification(prev.getCallClassification())
+ .setImsCallSessionId(prev.getImsCallSessionId())
+ .setImsCallServiceType(prev.getImsCallServiceType())
+ .setImsCallType(prev.getImsCallType()).build());
+ } else {
+ log("There is no active call to report CallQaulity");
+ return;
+ }
for (Record r : mRecords) {
if (r.matchTelephonyCallbackEvent(
TelephonyCallback.EVENT_CALL_ATTRIBUTES_CHANGED)
&& idMatch(r, subId, phoneId)) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ r.callback.onCallStatesChanged(mCallStateLists.get(phoneId));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2991,7 +3080,6 @@
pw.println("mSrvccState=" + mSrvccState[i]);
pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
pw.println("mCallQuality=" + mCallQuality[i]);
- pw.println("mCallAttributes=" + mCallAttributes[i]);
pw.println("mCallNetworkType=" + mCallNetworkType[i]);
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 35b46c1..50be458 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,6 +87,7 @@
import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
+import static android.os.Process.killProcessGroup;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
@@ -952,13 +953,6 @@
}
return false;
}
-
- boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
- if (app == null || app.getThread() != null) {
- return false;
- }
- return doRemoveInternal(pid, app);
- }
}
private final PendingStartActivityUids mPendingStartActivityUids;
@@ -990,7 +984,7 @@
* method.
*/
@GuardedBy("this")
- void removePidLocked(int pid, ProcessRecord app) {
+ boolean removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1001,26 +995,6 @@
}
mAtmInternal.onProcessUnMapped(pid);
}
- }
-
- /**
- * Removes the process record from the map if it doesn't have a thread.
- * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
- * method.
- */
- @GuardedBy("this")
- private boolean removePidIfNoThreadLocked(ProcessRecord app) {
- final boolean removed;
- final int pid = app.getPid();
- synchronized (mPidsSelfLocked) {
- removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
- }
- if (removed) {
- synchronized (sActiveProcessInfoSelfLocked) {
- sActiveProcessInfoSelfLocked.remove(pid);
- }
- mAtmInternal.onProcessUnMapped(pid);
- }
return removed;
}
@@ -2364,7 +2338,7 @@
mAppErrors = null;
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
- mBatteryStatsService = null;
+ mBatteryStatsService = mInjector.getBatteryStatsService();
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2379,7 +2353,7 @@
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
- mServices = null;
+ mServices = mInjector.getActiveServices(this);
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -4771,7 +4745,7 @@
@GuardedBy("this")
void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
final int pid = app.getPid();
- boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
+ boolean gone = isKillTimeout || removePidLocked(pid, app);
if (gone) {
if (isKillTimeout) {
@@ -4852,7 +4826,7 @@
}
@GuardedBy("this")
- private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
+ private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// Find the application record that is being attached... either via
@@ -4917,7 +4891,7 @@
// Ignore exceptions.
}
}
- return false;
+ return;
}
// If this application record is still attached to a previous
@@ -4942,7 +4916,7 @@
mProcessList.startProcessLocked(app,
new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
ZYGOTE_POLICY_FLAG_EMPTY);
- return false;
+ return;
}
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4965,8 +4939,6 @@
app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
}
- mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5132,7 +5104,7 @@
app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
+ return;
}
// Remove this record from the list of starting applications.
@@ -5140,91 +5112,6 @@
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
-
- boolean badApp = false;
- boolean didSomething = false;
-
- // See if the top visible activity is waiting to run in this process...
- if (normalMode) {
- try {
- didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
- badApp = true;
- }
- }
-
- // Find any services that should be running in this process...
- if (!badApp) {
- try {
- didSomething |= mServices.attachApplicationLocked(app, processName);
- checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
- badApp = true;
- }
- }
-
- // Check if a next-broadcast receiver is in this process...
- if (!badApp) {
- try {
- for (BroadcastQueue queue : mBroadcastQueues) {
- didSomething |= queue.onApplicationAttachedLocked(app);
- }
- checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
- } catch (Exception e) {
- // If the app died trying to launch the receiver we declare it 'bad'
- Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
- badApp = true;
- }
- }
-
- // Check whether the next backup agent is in this process...
- if (!badApp && backupTarget != null && backupTarget.app == app) {
- if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
- "New app is backup target, launching agent for " + app);
- notifyPackageUse(backupTarget.appInfo.packageName,
- PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
- try {
- thread.scheduleCreateBackupAgent(backupTarget.appInfo,
- backupTarget.backupMode, backupTarget.userId,
- backupTarget.backupDestination);
- } catch (Exception e) {
- Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
- badApp = true;
- }
- }
-
- if (badApp) {
- app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
- true);
- handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
- return false;
- }
-
- if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
- checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
- }
-
-
- final HostingRecord hostingRecord = app.getHostingRecord();
- String shortAction = getShortAction(hostingRecord.getAction());
- FrameworkStatsLog.write(
- FrameworkStatsLog.PROCESS_START_TIME,
- app.info.uid,
- pid,
- app.info.packageName,
- FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
- app.getStartElapsedTime(),
- (int) (bindApplicationTimeMillis - app.getStartUptime()),
- (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
- hostingRecord.getType(),
- hostingRecord.getName(),
- shortAction,
- HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
- HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
- return true;
}
@Override
@@ -5241,6 +5128,143 @@
}
}
+ private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
+ final long startTime = SystemClock.uptimeMillis();
+ // Find the application record that is being attached... either via
+ // the pid if we are running in multiple processes, or just pull the
+ // next app record if we are emulating process with anonymous threads.
+ final ProcessRecord app;
+ synchronized (mPidsSelfLocked) {
+ app = mPidsSelfLocked.get(pid);
+ }
+
+ if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
+ mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+ } else {
+ Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ + ". Uid: " + uid);
+ killProcess(pid);
+ killProcessGroup(uid, pid);
+ mProcessList.noteAppKill(pid, uid,
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+ ApplicationExitInfo.SUBREASON_UNKNOWN,
+ "wrong startSeq");
+ app.killLocked("unexpected process record",
+ ApplicationExitInfo.REASON_OTHER, true);
+ return;
+ }
+
+ synchronized (this) {
+ final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+ final String processName = app.processName;
+ boolean badApp = false;
+ boolean didSomething = false;
+
+ // See if the top visible activity is waiting to run in this process...
+ if (normalMode) {
+ try {
+ didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Find any services that should be running in this process...
+ if (!badApp) {
+ try {
+ didSomething |= mServices.attachApplicationLocked(app, processName);
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after mServices.attachApplicationLocked");
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check if a next-broadcast receiver is in this process...
+ if (!badApp) {
+ try {
+ for (BroadcastQueue queue : mBroadcastQueues) {
+ didSomething |= queue.onApplicationAttachedLocked(app);
+ }
+ checkTime(startTime, "finishAttachApplicationInner: "
+ + "after dispatching broadcasts");
+ } catch (Exception e) {
+ // If the app died trying to launch the receiver we declare it 'bad'
+ Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+ badApp = true;
+ }
+ }
+
+ // Check whether the next backup agent is in this process...
+ final BackupRecord backupTarget = mBackupTargets.get(app.userId);
+ if (!badApp && backupTarget != null && backupTarget.app == app) {
+ if (DEBUG_BACKUP) {
+ Slog.v(TAG_BACKUP,
+ "New app is backup target, launching agent for " + app);
+ }
+
+ notifyPackageUse(backupTarget.appInfo.packageName,
+ PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+ try {
+ app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
+ backupTarget.backupMode, backupTarget.userId,
+ backupTarget.backupDestination);
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+ badApp = true;
+ }
+ }
+
+ if (badApp) {
+ app.killLocked("error during init",
+ ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
+ handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+ return;
+ }
+
+ if (!didSomething) {
+ updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
+ }
+
+ final HostingRecord hostingRecord = app.getHostingRecord();
+ final String shortAction = getShortAction(hostingRecord.getAction());
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.PROCESS_START_TIME,
+ app.info.uid,
+ pid,
+ app.info.packageName,
+ FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+ app.getStartElapsedTime(),
+ (int) (app.getBindApplicationTime() - app.getStartUptime()),
+ (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+ hostingRecord.getType(),
+ hostingRecord.getName(),
+ shortAction,
+ HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+ HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+ }
+ }
+
+ @Override
+ public final void finishAttachApplication(long startSeq) {
+ final int pid = Binder.getCallingPid();
+ final int uid = Binder.getCallingUid();
+
+ if (pid == MY_PID && uid == SYSTEM_UID) {
+ return;
+ }
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ finishAttachApplicationInner(startSeq, uid, pid);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
/**
* @return The last part of the string of an intent's action.
*/
@@ -18805,6 +18829,21 @@
return new ProcessList();
}
+ /**
+ * Returns the {@link BatteryStatsService} instance
+ */
+ public BatteryStatsService getBatteryStatsService() {
+ return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
+ BackgroundThread.get().getHandler());
+ }
+
+ /**
+ * Returns the {@link ActiveServices} instance
+ */
+ public ActiveServices getActiveServices(ActivityManagerService service) {
+ return new ActiveServices(service);
+ }
+
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 04ba757..1eebd01 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,6 +153,11 @@
"bcast_extra_running_urgent_process_queues";
private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
+ public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
+ private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
+ "bcast_max_consecutive_urgent_dispatches";
+ private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
+
/**
* For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
* to dispatch to a "running" process queue before we retire them back to
@@ -333,6 +338,9 @@
EXTRA_RUNNING_URGENT_PROCESS_QUEUES = getDeviceConfigInt(
KEY_EXTRA_RUNNING_URGENT_PROCESS_QUEUES,
DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES);
+ MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
+ KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+ DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0f9c775..66d7fc9 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -147,6 +147,12 @@
private boolean mActiveViaColdStart;
/**
+ * Number of consecutive urgent broadcasts that have been dispatched
+ * since the last non-urgent dispatch.
+ */
+ private int mActiveCountConsecutiveUrgent;
+
+ /**
* Count of pending broadcasts of these various flavors.
*/
private int mCountForeground;
@@ -546,19 +552,47 @@
* {@link #isEmpty()} being false.
*/
SomeArgs removeNextBroadcast() {
- ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
+ if (queue == mPendingUrgent) {
+ mActiveCountConsecutiveUrgent++;
+ } else {
+ mActiveCountConsecutiveUrgent = 0;
+ }
return queue.removeFirst();
}
@Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
- if (!mPendingUrgent.isEmpty()) {
- return mPendingUrgent;
- } else if (!mPending.isEmpty()) {
- return mPending;
+ ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
+ ArrayDeque<SomeArgs> nextNormal = null;
+ if (!mPending.isEmpty()) {
+ nextNormal = mPending;
} else if (!mPendingOffload.isEmpty()) {
- return mPendingOffload;
+ nextNormal = mPendingOffload;
}
- return null;
+ // nothing urgent pending, no further decisionmaking
+ if (nextUrgent == null) {
+ return nextNormal;
+ }
+ // nothing but urgent pending, also no further decisionmaking
+ if (nextNormal == null) {
+ return nextUrgent;
+ }
+
+ // Starvation mitigation: although we prioritize urgent broadcasts by default,
+ // we allow non-urgent deliveries to make steady progress even if urgent
+ // broadcasts are arriving faster than they can be dispatched.
+ //
+ // We do not try to defer to the next non-urgent broadcast if that broadcast
+ // is ordered and still blocked on delivery to other recipients.
+ final SomeArgs nextNormalArgs = nextNormal.peekFirst();
+ final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
+ final int nextNormalIndex = nextNormalArgs.argi1;
+ final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
+ final boolean canTakeNormal =
+ mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
+ && rNormal.enqueueTime <= rUrgent.enqueueTime
+ && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
+ return canTakeNormal ? nextNormal : nextUrgent;
}
/**
@@ -710,6 +744,18 @@
}
}
+ private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
+ final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
+
+ // We might be blocked waiting for other receivers to finish,
+ // typically for an ordered broadcast or priority traunches
+ if (r.terminalCount < blockedUntilTerminalCount
+ && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Update {@link #getRunnableAt()} if it's currently invalidated.
*/
@@ -718,13 +764,11 @@
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
- final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
final long runnableAt = r.enqueueTime;
- // We might be blocked waiting for other receivers to finish,
- // typically for an ordered broadcast or priority traunches
- if (r.terminalCount < blockedUntilTerminalCount
- && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
+ // If we're specifically queued behind other ordered dispatch activity,
+ // we aren't runnable yet
+ if (blockedOnOrderedDispatch(r, index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index ecea96e..937bbc9c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
}
@GuardedBy("mService")
- private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+ String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0a8c640..4706c268 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,6 +200,11 @@
private volatile long mStartElapsedTime;
/**
+ * When the process was sent the bindApplication request
+ */
+ private volatile long mBindApplicationTime;
+
+ /**
* This will be same as {@link #uid} usually except for some apps used during factory testing.
*/
private volatile int mStartUid;
@@ -739,6 +744,10 @@
return mStartElapsedTime;
}
+ long getBindApplicationTime() {
+ return mBindApplicationTime;
+ }
+
int getStartUid() {
return mStartUid;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 4d86140..2ea49b3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -100,7 +100,6 @@
import android.util.IntArray;
import android.util.Pair;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -177,7 +176,8 @@
static final int START_USER_SWITCH_FG_MSG = 120;
static final int COMPLETE_USER_SWITCH_MSG = 130;
static final int USER_COMPLETED_EVENT_MSG = 140;
- static final int USER_VISIBILITY_CHANGED_MSG = 150;
+
+ private static final int NO_ARG2 = 0;
// Message constant to clear {@link UserJourneySession} from {@link mUserIdToUserJourneyMap} if
// the user journey, defined in the UserLifecycleJourneyReported atom for statsd, is not
@@ -437,20 +437,6 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
- // TODO(b/244333150) remove this array and let UserVisibilityMediator call the listeners
- // directly, as that class should be responsible for all user visibility logic (for example,
- // when the foreground user is switched out, its profiles also become invisible)
- /**
- * List of visible users (as defined by {@link UserManager#isUserVisible()}).
- *
- * <p>It's only used to call {@link UserManagerInternal} when the visibility is changed upon
- * the user starting or stopping.
- *
- * <p>Note: only the key is used, not the value.
- */
- @GuardedBy("mLock")
- private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
-
private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
@@ -1092,24 +1078,11 @@
// instead.
userManagerInternal.unassignUserFromDisplayOnStop(userId);
- final boolean visibilityChanged;
- boolean visibleBefore;
- synchronized (mLock) {
- visibleBefore = mVisibleUsers.get(userId);
- if (visibleBefore) {
- deleteVisibleUserLocked(userId);
- visibilityChanged = true;
- } else {
- visibilityChanged = false;
- }
- }
-
updateStartedUserArrayLU();
final boolean allowDelayedLockingCopied = allowDelayedLocking;
Runnable finishUserStoppingAsync = () ->
- mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied,
- visibilityChanged));
+ mHandler.post(() -> finishUserStopping(userId, uss, allowDelayedLockingCopied));
if (mInjector.getUserManager().isPreCreated(userId)) {
finishUserStoppingAsync.run();
@@ -1146,22 +1119,8 @@
}
}
- private void addVisibleUserLocked(@UserIdInt int userId) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "adding %d to mVisibleUsers", userId);
- }
- mVisibleUsers.put(userId, true);
- }
-
- private void deleteVisibleUserLocked(@UserIdInt int userId) {
- if (DEBUG_MU) {
- Slogf.d(TAG, "deleting %d from mVisibleUsers", userId);
- }
- mVisibleUsers.delete(userId);
- }
-
private void finishUserStopping(final int userId, final UserState uss,
- final boolean allowDelayedLocking, final boolean visibilityChanged) {
+ final boolean allowDelayedLocking) {
EventLog.writeEvent(EventLogTags.UC_FINISH_USER_STOPPING, userId);
synchronized (mLock) {
if (uss.state != UserState.STATE_STOPPING) {
@@ -1179,9 +1138,6 @@
BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
Integer.toString(userId), userId);
mInjector.getSystemServiceManager().onUserStopping(userId);
- if (visibilityChanged) {
- mInjector.onUserVisibilityChanged(userId, /* visible= */ false);
- }
Runnable finishUserStoppedAsync = () ->
mHandler.post(() -> finishUserStopped(uss, allowDelayedLocking));
@@ -1655,25 +1611,13 @@
userInfo.profileGroupId, foreground, displayId);
t.traceEnd();
- boolean visible;
- switch (result) {
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE:
- visible = true;
- break;
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE:
- visible = false;
- break;
- default:
- Slogf.wtf(TAG, "Wrong result from assignUserToDisplayOnStart(): %d", result);
- // Fall through
- case UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE:
- Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
- (foreground ? "fg" : "bg"), userId, displayId,
- UserManagerInternal.userAssignmentResultToString(result));
- return false;
+ if (result == UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE) {
+ Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
+ (foreground ? "fg" : "bg"), userId, displayId,
+ UserManagerInternal.userAssignmentResultToString(result));
+ return false;
}
-
// TODO(b/239982558): might need something similar for bg users on secondary display
if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
@@ -1724,15 +1668,7 @@
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEvent(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
boolean userSwitchUiEnabled;
- // TODO(b/244333150): temporary state until the callback logic is moved to
- // UserVisibilityManager
- int previousCurrentUserId; boolean notifyPreviousCurrentUserId;
synchronized (mLock) {
- previousCurrentUserId = mCurrentUserId;
- notifyPreviousCurrentUserId = mVisibleUsers.get(previousCurrentUserId);
- if (notifyPreviousCurrentUserId) {
- deleteVisibleUserLocked(previousCurrentUserId);
- }
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
userSwitchUiEnabled = mUserSwitchUiEnabled;
@@ -1753,10 +1689,6 @@
mInjector.getWindowManager().lockNow(null);
}
}
- if (notifyPreviousCurrentUserId) {
- mHandler.sendMessage(mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG,
- previousCurrentUserId, 0));
- }
} else {
final Integer currentUserIdInt = mCurrentUserId;
@@ -1768,12 +1700,6 @@
}
t.traceEnd();
- if (visible) {
- synchronized (mLock) {
- addVisibleUserLocked(userId);
- }
- }
-
// Make sure user is in the started state. If it is currently
// stopping, we need to knock that off.
if (uss.state == UserState.STATE_STOPPING) {
@@ -1810,20 +1736,10 @@
// Booting up a new user, need to tell system services about it.
// Note that this is on the same handler as scheduling of broadcasts,
// which is important because it needs to go first.
- mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId,
- visible ? 1 : 0));
+ mHandler.sendMessage(mHandler.obtainMessage(USER_START_MSG, userId, NO_ARG2));
t.traceEnd();
}
- if (visible) {
- // User was already running and became visible (for example, when switching to a
- // user that was started in the background before), so it's necessary to explicitly
- // notify the services (while when the user starts from BOOTING, USER_START_MSG
- // takes care of that.
- mHandler.sendMessage(
- mHandler.obtainMessage(USER_VISIBILITY_CHANGED_MSG, userId, 1));
- }
-
t.traceBegin("sendMessages");
if (foreground) {
mHandler.sendMessage(mHandler.obtainMessage(USER_CURRENT_MSG, userId, oldUserId));
@@ -2248,11 +2164,6 @@
uss.switching = false;
stopGuestOrEphemeralUserIfBackground(oldUserId);
stopUserOnSwitchIfEnforced(oldUserId);
- if (oldUserId == UserHandle.USER_SYSTEM) {
- // System user is never stopped, but its visibility is changed (as it is brought to the
- // background)
- updateSystemUserVisibility(t, /* visible= */ false);
- }
t.traceEnd(); // end continueUserSwitch
}
@@ -2614,27 +2525,10 @@
// Don't need to call on HSUM because it will be called when the system user is
// restarted on background
mInjector.onUserStarting(UserHandle.USER_SYSTEM);
- mInjector.onUserVisibilityChanged(UserHandle.USER_SYSTEM, /* visible= */ true);
+ mInjector.onSystemUserVisibilityChanged(/* visible= */ true);
}
}
- private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
- t.traceBegin("update-system-userVisibility-" + visible);
- if (DEBUG_MU) {
- Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
- }
- int userId = UserHandle.USER_SYSTEM;
- synchronized (mLock) {
- if (visible) {
- addVisibleUserLocked(userId);
- } else {
- deleteVisibleUserLocked(userId);
- }
- }
- mInjector.onUserVisibilityChanged(userId, visible);
- t.traceEnd();
- }
-
/**
* Refreshes the internal caches related to user profiles.
*
@@ -3032,9 +2926,6 @@
proto.end(uToken);
}
}
- for (int i = 0; i < mVisibleUsers.size(); i++) {
- proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
- }
proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId);
for (int i = 0; i < mCurrentProfileIds.length; i++) {
proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]);
@@ -3094,7 +2985,6 @@
pw.println(" mSwitchingToSystemUserMessage: " + mSwitchingToSystemUserMessage);
}
pw.println(" mLastUserUnlockingUptime: " + mLastUserUnlockingUptime);
- pw.println(" mVisibleUsers: " + mVisibleUsers);
}
}
@@ -3212,10 +3102,6 @@
case COMPLETE_USER_SWITCH_MSG:
completeUserSwitch(msg.arg1);
break;
- case USER_VISIBILITY_CHANGED_MSG:
- mInjector.onUserVisibilityChanged(/* userId= */ msg.arg1,
- /* visible= */ msg.arg2 == 1);
- break;
}
return false;
}
@@ -3750,8 +3636,8 @@
getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
}
- void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- getUserManagerInternal().onUserVisibilityChanged(userId, visible);
+ void onSystemUserVisibilityChanged(boolean visible) {
+ getUserManagerInternal().onSystemUserVisibilityChanged(visible);
}
}
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b92c163..f650255 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -40,6 +40,7 @@
import android.app.GameState;
import android.app.IGameManagerService;
import android.app.IGameModeListener;
+import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -76,6 +77,7 @@
import android.util.AttributeSet;
import android.util.KeyValueListParser;
import android.util.Slog;
+import android.util.StatsEvent;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
@@ -104,6 +106,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Service to manage game related features.
@@ -338,7 +341,18 @@
+ " and userId " + userId);
break;
}
+ if (mHandler.hasMessages(CANCEL_GAME_LOADING_MODE)) {
+ mHandler.removeMessages(CANCEL_GAME_LOADING_MODE);
+ }
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, isLoading);
+ if (isLoading) {
+ int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
+ loadingBoostDuration = loadingBoostDuration > 0 ? loadingBoostDuration
+ : LOADING_BOOST_MAX_DURATION;
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(CANCEL_GAME_LOADING_MODE),
+ loadingBoostDuration);
+ }
}
break;
}
@@ -438,6 +452,7 @@
public void setGameState(String packageName, @NonNull GameState gameState,
@UserIdInt int userId) {
if (!isPackageGame(packageName, userId)) {
+ Slog.d(TAG, "No-op for attempt to set game state for non-game app: " + packageName);
// Restrict to games only.
return;
}
@@ -921,6 +936,7 @@
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
mService.onBootCompleted();
+ mService.registerStatsCallbacks();
}
}
@@ -964,11 +980,8 @@
}
}
- private @GameMode int[] getAvailableGameModesUnchecked(String packageName) {
- final GamePackageConfiguration config;
- synchronized (mDeviceConfigLock) {
- config = mConfigs.get(packageName);
- }
+ private @GameMode int[] getAvailableGameModesUnchecked(String packageName, int userId) {
+ final GamePackageConfiguration config = getConfig(packageName, userId);
if (config == null) {
return new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM};
}
@@ -991,9 +1004,13 @@
*/
@Override
@RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
- public @GameMode int[] getAvailableGameModes(String packageName) throws SecurityException {
+ public @GameMode int[] getAvailableGameModes(String packageName, int userId)
+ throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- return getAvailableGameModesUnchecked(packageName);
+ if (!isPackageGame(packageName, userId)) {
+ return new int[]{};
+ }
+ return getAvailableGameModesUnchecked(packageName, userId);
}
private @GameMode int getGameModeFromSettingsUnchecked(String packageName,
@@ -1060,7 +1077,6 @@
// Check the caller has the necessary permission.
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- // Restrict to games only.
if (!isPackageGame(packageName, userId)) {
return null;
}
@@ -1090,7 +1106,7 @@
} else {
return new GameModeInfo.Builder()
.setActiveGameMode(activeGameMode)
- .setAvailableGameModes(getAvailableGameModesUnchecked(packageName))
+ .setAvailableGameModes(getAvailableGameModesUnchecked(packageName, userId))
.build();
}
}
@@ -1104,9 +1120,11 @@
public void setGameMode(String packageName, @GameMode int gameMode, int userId)
throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
-
- if (!isPackageGame(packageName, userId) || gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
- // Restrict to games and valid game modes only.
+ if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ Slog.d(TAG, "No-op for attempt to set UNSUPPORTED mode for app: " + packageName);
+ return;
+ } else if (!isPackageGame(packageName, userId)) {
+ Slog.d(TAG, "No-op for attempt to set game mode for non-game app: " + packageName);
return;
}
int fromGameMode;
@@ -1139,6 +1157,15 @@
sendUserMessage(userId, WRITE_SETTINGS, "SET_GAME_MODE", WRITE_DELAY_MILLIS);
sendUserMessage(userId, WRITE_GAME_MODE_INTERVENTION_LIST_FILE,
"SET_GAME_MODE", 0 /*delayMillis*/);
+ int gameUid = -1;
+ try {
+ gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException ex) {
+ Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CHANGED, gameUid,
+ Binder.getCallingUid(), gameModeToStatsdGameMode(fromGameMode),
+ gameModeToStatsdGameMode(gameMode));
}
/**
@@ -1205,17 +1232,16 @@
Binder.getCallingUid(), userId, false, true, "notifyGraphicsEnvironmentSetup",
"com.android.server.app.GameManagerService");
- // Restrict to games only.
- if (!isPackageGame(packageName, userId)) {
- return;
- }
-
if (!isValidPackageName(packageName, userId)) {
+ Slog.d(TAG, "No-op for attempt to notify graphics env setup for different package"
+ + "than caller with uid: " + Binder.getCallingUid());
return;
}
final int gameMode = getGameMode(packageName, userId);
if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ Slog.d(TAG, "No-op for attempt to notify graphics env setup for non-game app: "
+ + packageName);
return;
}
int loadingBoostDuration = getLoadingBoostDuration(packageName, userId);
@@ -1330,6 +1356,11 @@
GameModeConfiguration gameModeConfig, int userId)
throws SecurityException, IllegalArgumentException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
+ if (!isPackageGame(packageName, userId)) {
+ Slog.d(TAG, "No-op for attempt to update custom game mode for non-game app: "
+ + packageName);
+ return;
+ }
synchronized (mLock) {
if (!mSettings.containsKey(userId)) {
throw new IllegalArgumentException("User " + userId + " wasn't started");
@@ -1353,11 +1384,21 @@
}
GamePackageConfiguration.GameModeConfiguration internalConfig =
configOverride.getOrAddDefaultGameModeConfiguration(GameManager.GAME_MODE_CUSTOM);
+ int gameUid = -1;
+ try {
+ gameUid = mPackageManager.getPackageUidAsUser(packageName, userId);
+ } catch (NameNotFoundException ex) {
+ Slog.d(TAG, "Cannot find the UID for package " + packageName + " under user " + userId);
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.GAME_MODE_CONFIGURATION_CHANGED, gameUid,
+ Binder.getCallingUid(), gameModeToStatsdGameMode(GameManager.GAME_MODE_CUSTOM),
+ internalConfig.getScaling(), gameModeConfig.getScalingFactor(),
+ internalConfig.getFps(), gameModeConfig.getFpsOverride());
internalConfig.updateFromPublicGameModeConfig(gameModeConfig);
Slog.i(TAG, "Updated custom game mode config for package: " + packageName
+ " with FPS=" + internalConfig.getFps() + ";Scaling="
- + internalConfig.getScaling());
+ + internalConfig.getScaling() + " under user " + userId);
}
/**
@@ -1618,11 +1659,6 @@
public void resetGameModeConfigOverride(String packageName, @UserIdInt int userId,
@GameMode int gameModeToReset) throws SecurityException {
checkPermission(Manifest.permission.MANAGE_GAME_MODE);
- final GamePackageConfiguration deviceConfig;
- synchronized (mDeviceConfigLock) {
- deviceConfig = mConfigs.get(packageName);
- }
-
// resets GamePackageConfiguration of a given packageName.
// If a gameMode is specified, only reset the GameModeConfiguration of the gameMode.
synchronized (mLock) {
@@ -1938,13 +1974,99 @@
LocalServices.addService(GameManagerInternal.class, new LocalService());
}
- private String dumpDeviceConfigs() {
- StringBuilder out = new StringBuilder();
- for (String key : mConfigs.keySet()) {
- out.append("[\nName: ").append(key)
- .append("\nConfig: ").append(mConfigs.get(key).toString()).append("\n]");
+ private void registerStatsCallbacks() {
+ final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.GAME_MODE_INFO,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.GAME_MODE_CONFIGURATION,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
+ statsManager.setPullAtomCallback(
+ FrameworkStatsLog.GAME_MODE_LISTENER,
+ null, // use default PullAtomMetadata values
+ BackgroundThread.getExecutor(),
+ this::onPullAtom);
+ }
+
+ private int onPullAtom(int atomTag, @NonNull List<StatsEvent> data) {
+ if (atomTag == FrameworkStatsLog.GAME_MODE_INFO
+ || atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+ int userId = ActivityManager.getCurrentUser();
+ Set<String> packages;
+ synchronized (mDeviceConfigLock) {
+ packages = mConfigs.keySet();
+ }
+ for (String p : packages) {
+ GamePackageConfiguration config = getConfig(p, userId);
+ if (config == null) {
+ continue;
+ }
+ int uid = -1;
+ try {
+ uid = mPackageManager.getPackageUidAsUser(p, userId);
+ } catch (NameNotFoundException ex) {
+ Slog.d(TAG,
+ "Cannot find UID for package " + p + " under user handle id " + userId);
+ }
+ if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
+ data.add(
+ FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
+ gameModesToStatsdGameModes(config.getOptedInGameModes()),
+ gameModesToStatsdGameModes(config.getAvailableGameModes())));
+ } else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
+ for (int gameMode : config.getAvailableGameModes()) {
+ GamePackageConfiguration.GameModeConfiguration modeConfig =
+ config.getGameModeConfiguration(gameMode);
+ if (modeConfig != null) {
+ data.add(FrameworkStatsLog.buildStatsEvent(
+ FrameworkStatsLog.GAME_MODE_CONFIGURATION, uid,
+ gameModeToStatsdGameMode(gameMode), modeConfig.getFps(),
+ modeConfig.getScaling()));
+ }
+ }
+ }
+ }
+ } else if (atomTag == FrameworkStatsLog.GAME_MODE_LISTENER) {
+ synchronized (mGameModeListenerLock) {
+ data.add(FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_LISTENER,
+ mGameModeListeners.size()));
+ }
}
- return out.toString();
+ return android.app.StatsManager.PULL_SUCCESS;
+ }
+
+ private static int[] gameModesToStatsdGameModes(int[] modes) {
+ if (modes == null) {
+ return null;
+ }
+ int[] statsdModes = new int[modes.length];
+ int i = 0;
+ for (int mode : modes) {
+ statsdModes[i++] = gameModeToStatsdGameMode(mode);
+ }
+ return statsdModes;
+ }
+
+ private static int gameModeToStatsdGameMode(int mode) {
+ switch (mode) {
+ case GameManager.GAME_MODE_BATTERY:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_BATTERY;
+ case GameManager.GAME_MODE_PERFORMANCE:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_PERFORMANCE;
+ case GameManager.GAME_MODE_CUSTOM:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_CUSTOM;
+ case GameManager.GAME_MODE_STANDARD:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_STANDARD;
+ case GameManager.GAME_MODE_UNSUPPORTED:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSUPPORTED;
+ default:
+ return FrameworkStatsLog.GAME_MODE_CONFIGURATION__GAME_MODE__GAME_MODE_UNSPECIFIED;
+ }
}
private static int gameStateModeToStatsdGameState(int mode) {
diff --git a/services/core/java/com/android/server/app/GameManagerShellCommand.java b/services/core/java/com/android/server/app/GameManagerShellCommand.java
index cdbffbe..aa9b77c 100644
--- a/services/core/java/com/android/server/app/GameManagerShellCommand.java
+++ b/services/core/java/com/android/server/app/GameManagerShellCommand.java
@@ -105,7 +105,9 @@
ServiceManager.getServiceOrThrow(Context.GAME_SERVICE));
boolean batteryModeSupported = false;
boolean perfModeSupported = false;
- int[] modes = service.getAvailableGameModes(packageName);
+ int userId = userIdStr != null ? Integer.parseInt(userIdStr)
+ : ActivityManager.getCurrentUser();
+ int[] modes = service.getAvailableGameModes(packageName, userId);
for (int mode : modes) {
if (mode == GameManager.GAME_MODE_PERFORMANCE) {
perfModeSupported = true;
@@ -113,8 +115,6 @@
batteryModeSupported = true;
}
}
- int userId = userIdStr != null ? Integer.parseInt(userIdStr)
- : ActivityManager.getCurrentUser();
switch (gameMode.toLowerCase()) {
case "1":
case "standard":
@@ -223,7 +223,7 @@
boolean batteryModeSupported = false;
boolean perfModeSupported = false;
- int [] modes = gameManagerService.getAvailableGameModes(packageName);
+ int [] modes = gameManagerService.getAvailableGameModes(packageName, userId);
for (int mode : modes) {
if (mode == GameManager.GAME_MODE_PERFORMANCE) {
diff --git a/services/core/java/com/android/server/app/TEST_MAPPING b/services/core/java/com/android/server/app/TEST_MAPPING
index feb2b4f..82840ee 100644
--- a/services/core/java/com/android/server/app/TEST_MAPPING
+++ b/services/core/java/com/android/server/app/TEST_MAPPING
@@ -9,6 +9,23 @@
]
},
{
+ "name": "CtsStatsdAtomHostTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "include-filter": "android.cts.statsdatom.gamemanager"
+ }
+ ],
+ "file_patterns": [
+ "(/|^)GameManagerService.java"
+ ]
+ },
+ {
"name": "FrameworksMockingServicesTests",
"options": [
{
diff --git a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
similarity index 97%
rename from services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
rename to services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index f6fff35..8d1da71 100644
--- a/services/core/java/com/android/server/appop/LegacyAppOpsServiceInterfaceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -20,7 +20,7 @@
import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
import static android.app.AppOpsManager.opRestrictsRead;
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
import android.Manifest;
import android.annotation.NonNull;
@@ -56,7 +56,7 @@
* Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
* In the future this class will also include mode callbacks and op restrictions.
*/
-public class LegacyAppOpsServiceInterfaceImpl implements AppOpsServiceInterface {
+public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
@@ -84,9 +84,9 @@
private static final int UID_ANY = -2;
- LegacyAppOpsServiceInterfaceImpl(PersistenceScheduler persistenceScheduler,
- @NonNull Object lock, Handler handler, Context context,
- SparseArray<int[]> switchedOps) {
+ AppOpsCheckingServiceImpl(PersistenceScheduler persistenceScheduler,
+ @NonNull Object lock, Handler handler, Context context,
+ SparseArray<int[]> switchedOps) {
this.mPersistenceScheduler = persistenceScheduler;
this.mLock = lock;
this.mHandler = handler;
@@ -456,7 +456,7 @@
final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
if (reportedPackageNames == null) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ AppOpsCheckingServiceImpl::notifyOpChanged,
this, callback, code, uid, (String) null));
} else {
@@ -464,7 +464,7 @@
for (int j = 0; j < reportedPackageCount; j++) {
final String reportedPackageName = reportedPackageNames.valueAt(j);
mHandler.sendMessage(PooledLambda.obtainMessage(
- LegacyAppOpsServiceInterfaceImpl::notifyOpChanged,
+ AppOpsCheckingServiceImpl::notifyOpChanged,
this, callback, code, uid, reportedPackageName));
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
new file mode 100644
index 0000000..d8d0d48
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -0,0 +1,209 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager.Mode;
+import android.util.ArraySet;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
+ * This interface also includes functions for added and removing op mode watchers.
+ * In the future this interface will also include op restrictions.
+ */
+public interface AppOpsCheckingServiceInterface {
+ /**
+ * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
+ * Returns an empty SparseIntArray if nothing is set.
+ * @param uid for which we need the app-ops and their modes.
+ */
+ SparseIntArray getNonDefaultUidModes(int uid);
+
+ /**
+ * Returns the app-op mode for a particular app-op of a uid.
+ * Returns default op mode if the op mode for particular uid and op is not set.
+ * @param uid user id for which we need the mode.
+ * @param op app-op for which we need the mode.
+ * @return mode of the app-op.
+ */
+ int getUidMode(int uid, int op);
+
+ /**
+ * Set the app-op mode for a particular uid and op.
+ * The mode is not set if the mode is the same as the default mode for the op.
+ * @param uid user id for which we want to set the mode.
+ * @param op app-op for which we want to set the mode.
+ * @param mode mode for the app-op.
+ * @return true if op mode is changed.
+ */
+ boolean setUidMode(int uid, int op, @Mode int mode);
+
+ /**
+ * Gets the app-op mode for a particular package.
+ * Returns default op mode if the op mode for the particular package is not set.
+ * @param packageName package name for which we need the op mode.
+ * @param op app-op for which we need the mode.
+ * @param userId user id associated with the package.
+ * @return the mode of the app-op.
+ */
+ int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
+
+ /**
+ * Sets the app-op mode for a particular package.
+ * @param packageName package name for which we need to set the op mode.
+ * @param op app-op for which we need to set the mode.
+ * @param mode the mode of the app-op.
+ * @param userId user id associated with the package.
+ *
+ */
+ void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+
+ /**
+ * Stop tracking any app-op modes for a package.
+ * @param packageName Name of the package for which we want to remove all mode tracking.
+ * @param userId user id associated with the package.
+ */
+ boolean removePackage(@NonNull String packageName, @UserIdInt int userId);
+
+ /**
+ * Stop tracking any app-op modes for this uid.
+ * @param uid user id for which we want to remove all tracking.
+ */
+ void removeUid(int uid);
+
+ /**
+ * Returns true if all uid modes for this uid are
+ * in default state.
+ * @param uid user id
+ */
+ boolean areUidModesDefault(int uid);
+
+ /**
+ * Returns true if all package modes for this package name are
+ * in default state.
+ * @param packageName package name.
+ * @param userId user id associated with the package.
+ */
+ boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+
+ /**
+ * Stop tracking app-op modes for all uid and packages.
+ */
+ void clearAllModes();
+
+ /**
+ * Registers changedListener to listen to op's mode change.
+ * @param changedListener the listener that must be trigger on the op's mode change.
+ * @param op op representing the app-op whose mode change needs to be listened to.
+ */
+ void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+
+ /**
+ * Registers changedListener to listen to package's app-op's mode change.
+ * @param changedListener the listener that must be trigger on the mode change.
+ * @param packageName of the package whose app-op's mode change needs to be listened to.
+ */
+ void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
+ @NonNull String packageName);
+
+ /**
+ * Stop the changedListener from triggering on any mode change.
+ * @param changedListener the listener that needs to be removed.
+ */
+ void removeListener(@NonNull OnOpModeChangedListener changedListener);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
+ * @param op app-op whose mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
+ * @param packageName of package whose app-op's mode change is being listened to.
+ */
+ ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ */
+ void notifyWatchersOfChange(int op, int uid);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed by triggering the change listener.
+ * @param changedListener the change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param packageName package name that is associated with the app-op
+ */
+ void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
+ @Nullable String packageName);
+
+ /**
+ * Temporary API which will be removed once we can safely untangle the methods that use this.
+ * Notify that the app-op's mode is changed to all packages associated with the uid by
+ * triggering the appropriate change listener.
+ * @param op App-op whose mode has changed
+ * @param uid user id associated with the app-op
+ * @param onlyForeground true if only watchers that
+ * @param callbackToIgnore callback that should be ignored.
+ */
+ void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
+ @Nullable OnOpModeChangedListener callbackToIgnore);
+
+ /**
+ * TODO: Move hasForegroundWatchers and foregroundOps into this.
+ * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param uid for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+
+ /**
+ * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
+ * foregroundOps.
+ * @param packageName for which the app-op's mode needs to be marked.
+ * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
+ * @param userId user id associated with the package.
+ * @return foregroundOps.
+ */
+ SparseBooleanArray evalForegroundPackageOps(String packageName,
+ SparseBooleanArray foregroundOps, @UserIdInt int userId);
+
+ /**
+ * Dump op mode and package mode listeners and their details.
+ * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
+ * app-op, only the watchers for that app-op are dumped.
+ * @param dumpUid uid for which we want to dump op mode watchers.
+ * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
+ * @param printWriter writer to dump to.
+ */
+ boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index adfd2af..af5b07e 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -42,7 +42,7 @@
private Context mContext;
private Handler mHandler;
- private AppOpsServiceInterface mAppOpsServiceInterface;
+ private AppOpsCheckingServiceInterface mAppOpsServiceInterface;
// Map from (Object token) to (int code) to (boolean restricted)
private final ArrayMap<Object, SparseBooleanArray> mGlobalRestrictions = new ArrayMap<>();
@@ -56,7 +56,7 @@
mUserRestrictionExcludedPackageTags = new ArrayMap<>();
public AppOpsRestrictionsImpl(Context context, Handler handler,
- AppOpsServiceInterface appOpsServiceInterface) {
+ AppOpsCheckingServiceInterface appOpsServiceInterface) {
mContext = context;
mHandler = handler;
mAppOpsServiceInterface = appOpsServiceInterface;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 7e00c32..39338c6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -18,55 +18,22 @@
import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
-import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
-import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
-import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
-import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
-import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
-import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
-import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
-import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_FOREGROUND;
-import static android.app.AppOpsManager.MODE_IGNORED;
-import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PLAY_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
-import static android.app.AppOpsManager.OP_VIBRATE;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
-import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
-import static android.app.AppOpsManager.OpEventProxyInfo;
-import static android.app.AppOpsManager.RestrictionBypass;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_BOOT_TIME_SAMPLING;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_RARELY_USED;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM;
import static android.app.AppOpsManager.SAMPLING_STRATEGY_UNIFORM_OPS;
-import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
import static android.app.AppOpsManager._NUM_OP;
-import static android.app.AppOpsManager.extractFlagsFromKey;
-import static android.app.AppOpsManager.extractUidStateFromKey;
-import static android.app.AppOpsManager.modeToName;
-import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
import static android.app.AppOpsManager.opRestrictsRead;
-import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
-import static android.content.Intent.ACTION_PACKAGE_REMOVED;
-import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
-import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -75,21 +42,16 @@
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
import android.app.AppOpsManager;
-import android.app.AppOpsManager.AttributedOpEntry;
import android.app.AppOpsManager.AttributionFlags;
import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.Mode;
-import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManagerInternal;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
import android.app.AsyncNotedAppOp;
import android.app.RuntimeAppOpAccessMessage;
import android.app.SyncNotedAppOp;
-import android.app.admin.DevicePolicyManagerInternal;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -97,15 +59,11 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PermissionInfo;
-import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
@@ -116,22 +74,14 @@
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
-import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.storage.StorageManagerInternal;
-import android.permission.PermissionManager;
-import android.provider.Settings;
import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.KeyValueListParser;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
-import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
@@ -143,59 +93,37 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.app.IAppOpsStartedCallback;
import com.android.internal.app.MessageSamplingConfig;
-import com.android.internal.compat.IPlatformCompat;
-import com.android.internal.os.Clock;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.LocalServices;
-import com.android.server.LockGuard;
-import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedAttribution;
import com.android.server.policy.AppOpsPolicy;
-import dalvik.annotation.optimization.NeverCompile;
-
-import libcore.util.EmptyArray;
-
import org.json.JSONException;
import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
-import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
-import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
-public class AppOpsService extends IAppOpsService.Stub implements PersistenceScheduler {
+/**
+ * The system service component to {@link AppOpsManager}.
+ */
+public class AppOpsService extends IAppOpsService.Stub {
+
+ private final AppOpsServiceInterface mAppOpsService;
+
static final String TAG = "AppOps";
static final boolean DEBUG = false;
@@ -204,57 +132,19 @@
*/
private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
- private static final int NO_VERSION = -1;
- /** Increment by one every time and add the corresponding upgrade logic in
- * {@link #upgradeLocked(int)} below. The first version was 1 */
- private static final int CURRENT_VERSION = 1;
-
- // Write at most every 30 minutes.
- static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
-
// Constant meaning that any UID should be matched when dispatching callbacks
private static final int UID_ANY = -2;
- private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
- OP_PLAY_AUDIO,
- OP_RECORD_AUDIO,
- OP_CAMERA,
- OP_VIBRATE,
- };
-
private static final int MAX_UNFORWARDED_OPS = 10;
- private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
private static final int RARELY_USED_PACKAGES_INITIALIZATION_DELAY_MILLIS = 300000;
final Context mContext;
- final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
- /**
- * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
- * objects
- */
- @GuardedBy("this")
- final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
- new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
-
- /**
- * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
- * new objects
- */
- @GuardedBy("this")
- final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
- new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
- MAX_UNUSED_POOLED_OBJECTS);
-
private final AppOpsManagerInternalImpl mAppOpsManagerInternal
= new AppOpsManagerInternalImpl();
- @Nullable private final DevicePolicyManagerInternal dpmi =
- LocalServices.getService(DevicePolicyManagerInternal.class);
-
- private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
- ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/**
* Registered callbacks, called from {@link #collectAsyncNotedOp}.
@@ -281,54 +171,9 @@
boolean mWriteNoteOpsScheduled;
- boolean mWriteScheduled;
- boolean mFastWriteScheduled;
- final Runnable mWriteRunner = new Runnable() {
- public void run() {
- synchronized (AppOpsService.this) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
- @Override protected Void doInBackground(Void... params) {
- writeState();
- return null;
- }
- };
- task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
- }
- }
- };
-
- @GuardedBy("this")
- @VisibleForTesting
- final SparseArray<UidState> mUidStates = new SparseArray<>();
-
- volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
-
- /*
- * These are app op restrictions imposed per user from various parties.
- */
- private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
- new ArrayMap<>();
-
- /*
- * These are app op restrictions imposed globally from various parties within the system.
- */
- private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
- new ArrayMap<>();
-
- SparseIntArray mProfileOwners;
-
private volatile CheckOpsDelegateDispatcher mCheckOpsDelegateDispatcher =
new CheckOpsDelegateDispatcher(/*policy*/ null, /*delegate*/ null);
- /**
- * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
- * changed
- */
- private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
-
- private ActivityManagerInternal mActivityManagerInternal;
/** Package sampled for message collection in the current session */
@GuardedBy("this")
@@ -362,545 +207,8 @@
/** Package Manager internal. Access via {@link #getPackageManagerInternal()} */
private @Nullable PackageManagerInternal mPackageManagerInternal;
- /** Interface for app-op modes.*/
- @VisibleForTesting AppOpsServiceInterface mAppOpsServiceInterface;
-
- /** Interface for app-op restrictions.*/
- @VisibleForTesting AppOpsRestrictions mAppOpsRestrictions;
-
- private AppOpsUidStateTracker mUidStateTracker;
-
- /** Hands the definition of foreground and uid states */
- @GuardedBy("this")
- public AppOpsUidStateTracker getUidStateTracker() {
- if (mUidStateTracker == null) {
- mUidStateTracker = new AppOpsUidStateTrackerImpl(
- LocalServices.getService(ActivityManagerInternal.class),
- mHandler,
- r -> {
- synchronized (AppOpsService.this) {
- r.run();
- }
- },
- Clock.SYSTEM_CLOCK, mConstants);
-
- mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
- this::onUidStateChanged);
- }
- return mUidStateTracker;
- }
-
- /**
- * All times are in milliseconds. These constants are kept synchronized with the system
- * global Settings. Any access to this class or its fields should be done while
- * holding the AppOpsService lock.
- */
- final class Constants extends ContentObserver {
-
- /**
- * How long we want for a drop in uid state from top to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
- */
- public long TOP_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from foreground to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
- */
- public long FG_SERVICE_STATE_SETTLE_TIME;
-
- /**
- * How long we want for a drop in uid state from background to settle before applying it.
- * @see Settings.Global#APP_OPS_CONSTANTS
- * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
- */
- public long BG_STATE_SETTLE_TIME;
-
- private final KeyValueListParser mParser = new KeyValueListParser(',');
- private ContentResolver mResolver;
-
- public Constants(Handler handler) {
- super(handler);
- updateConstants();
- }
-
- public void startMonitoring(ContentResolver resolver) {
- mResolver = resolver;
- mResolver.registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
- false, this);
- updateConstants();
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateConstants();
- }
-
- private void updateConstants() {
- String value = mResolver != null ? Settings.Global.getString(mResolver,
- Settings.Global.APP_OPS_CONSTANTS) : "";
-
- synchronized (AppOpsService.this) {
- try {
- mParser.setString(value);
- } catch (IllegalArgumentException e) {
- // Failed to parse the settings string, log this and move on
- // with defaults.
- Slog.e(TAG, "Bad app ops settings", e);
- }
- TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
- FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
- BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
- KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
- }
- }
-
- void dump(PrintWriter pw) {
- pw.println(" Settings:");
-
- pw.print(" "); pw.print(KEY_TOP_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" "); pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
- pw.println();
- pw.print(" "); pw.print(KEY_BG_STATE_SETTLE_TIME); pw.print("=");
- TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
- pw.println();
- }
- }
-
- @VisibleForTesting
- final Constants mConstants;
-
- @VisibleForTesting
- final class UidState {
- public final int uid;
-
- public ArrayMap<String, Ops> pkgOps;
-
- // true indicates there is an interested observer, false there isn't but it has such an op
- //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
- public SparseBooleanArray foregroundOps;
- public boolean hasForegroundWatchers;
-
- public UidState(int uid) {
- this.uid = uid;
- }
-
- public void clear() {
- mAppOpsServiceInterface.removeUid(uid);
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
- }
- pkgOps = null;
- }
-
- public boolean isDefault() {
- boolean areAllPackageModesDefault = true;
- if (pkgOps != null) {
- for (String packageName : pkgOps.keySet()) {
- if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
- UserHandle.getUserId(uid))) {
- areAllPackageModesDefault = false;
- break;
- }
- }
- }
- return (pkgOps == null || pkgOps.isEmpty())
- && mAppOpsServiceInterface.areUidModesDefault(uid)
- && areAllPackageModesDefault;
- }
-
- // Functions for uid mode access and manipulation.
- public SparseIntArray getNonDefaultUidModes() {
- return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
- }
-
- public int getUidMode(int op) {
- return mAppOpsServiceInterface.getUidMode(uid, op);
- }
-
- public boolean setUidMode(int op, int mode) {
- return mAppOpsServiceInterface.setUidMode(uid, op, mode);
- }
-
- @SuppressWarnings("GuardedBy")
- int evalMode(int op, int mode) {
- return getUidStateTracker().evalMode(uid, op, mode);
- }
-
- public void evalForegroundOps() {
- foregroundOps = null;
- foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
- if (pkgOps != null) {
- for (int i = pkgOps.size() - 1; i >= 0; i--) {
- foregroundOps = mAppOpsServiceInterface
- .evalForegroundPackageOps(pkgOps.valueAt(i).packageName, foregroundOps,
- UserHandle.getUserId(uid));
- }
- }
- hasForegroundWatchers = false;
- if (foregroundOps != null) {
- for (int i = 0; i < foregroundOps.size(); i++) {
- if (foregroundOps.valueAt(i)) {
- hasForegroundWatchers = true;
- break;
- }
- }
- }
- }
-
- @SuppressWarnings("GuardedBy")
- public int getState() {
- return getUidStateTracker().getUidState(uid);
- }
-
- @SuppressWarnings("GuardedBy")
- public void dump(PrintWriter pw, long nowElapsed) {
- getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
- }
- }
-
- final static class Ops extends SparseArray<Op> {
- final String packageName;
- final UidState uidState;
-
- /**
- * The restriction properties of the package. If {@code null} it could not have been read
- * yet and has to be refreshed.
- */
- @Nullable RestrictionBypass bypass;
-
- /** Lazily populated cache of attributionTags of this package */
- final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
-
- /**
- * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
- * than or equal to {@link #knownAttributionTags}.
- */
- final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
-
- Ops(String _packageName, UidState _uidState) {
- packageName = _packageName;
- uidState = _uidState;
- }
- }
-
- /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
- private static final class PackageVerificationResult {
-
- final RestrictionBypass bypass;
- final boolean isAttributionTagValid;
-
- PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
- this.bypass = bypass;
- this.isAttributionTagValid = isAttributionTagValid;
- }
- }
-
- final class Op {
- int op;
- int uid;
- final UidState uidState;
- final @NonNull String packageName;
-
- /** attributionTag -> AttributedOp */
- final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
-
- Op(UidState uidState, String packageName, int op, int uid) {
- this.op = op;
- this.uid = uid;
- this.uidState = uidState;
- this.packageName = packageName;
- }
-
- @Mode int getMode() {
- return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
- UserHandle.getUserId(this.uid));
- }
- void setMode(@Mode int mode) {
- mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
- UserHandle.getUserId(this.uid));
- }
-
- void removeAttributionsWithNoTime() {
- for (int i = mAttributions.size() - 1; i >= 0; i--) {
- if (!mAttributions.valueAt(i).hasAnyTime()) {
- mAttributions.removeAt(i);
- }
- }
- }
-
- private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
- @Nullable String attributionTag) {
- AttributedOp attributedOp;
-
- attributedOp = mAttributions.get(attributionTag);
- if (attributedOp == null) {
- attributedOp = new AttributedOp(AppOpsService.this, attributionTag, parent);
- mAttributions.put(attributionTag, attributedOp);
- }
-
- return attributedOp;
- }
-
- @NonNull OpEntry createEntryLocked() {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
- new ArrayMap<>(numAttributions);
- for (int i = 0; i < numAttributions; i++) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- @NonNull OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
- final int numAttributions = mAttributions.size();
-
- final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
- for (int i = 0; i < numAttributions; i++) {
- if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
- attributionEntries.put(mAttributions.keyAt(i),
- mAttributions.valueAt(i).createAttributedOpEntryLocked());
- break;
- }
- }
-
- return new OpEntry(op, getMode(), attributionEntries);
- }
-
- boolean isRunning() {
- final int numAttributions = mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if (mAttributions.valueAt(i).isRunning()) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
- final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
final AudioRestrictionManager mAudioRestrictionManager = new AudioRestrictionManager();
- final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
- /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
- public static final int ALL_OPS = -2;
-
- // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
- // Otherwise we can just use the IBinder object.
- private final IAppOpsCallback mCallback;
-
- ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
- int callingUid, int callingPid) {
- super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
- this.mCallback = callback;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ModeCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, getWatchingUid());
- sb.append(" flags=0x");
- sb.append(Integer.toHexString(getFlags()));
- switch (getWatchedOpCode()) {
- case OP_NONE:
- break;
- case ALL_OPS:
- sb.append(" op=(all)");
- break;
- default:
- sb.append(" op=");
- sb.append(opToName(getWatchedOpCode()));
- break;
- }
- sb.append(" from uid=");
- UserHandle.formatUid(sb, getCallingUid());
- sb.append(" pid=");
- sb.append(getCallingPid());
- sb.append('}');
- return sb.toString();
- }
-
- void unlinkToDeath() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingMode(mCallback);
- }
-
- @Override
- public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
- mCallback.opChanged(op, uid, packageName);
- }
- }
-
- final class ActiveCallback implements DeathRecipient {
- final IAppOpsActiveCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("ActiveCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingActive(mCallback);
- }
- }
-
- final class StartedCallback implements DeathRecipient {
- final IAppOpsStartedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("StartedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingStarted(mCallback);
- }
- }
-
- final class NotedCallback implements DeathRecipient {
- final IAppOpsNotedCallback mCallback;
- final int mWatchingUid;
- final int mCallingUid;
- final int mCallingPid;
-
- NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
- int callingPid) {
- mCallback = callback;
- mWatchingUid = watchingUid;
- mCallingUid = callingUid;
- mCallingPid = callingPid;
- try {
- mCallback.asBinder().linkToDeath(this, 0);
- } catch (RemoteException e) {
- /*ignored*/
- }
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(128);
- sb.append("NotedCallback{");
- sb.append(Integer.toHexString(System.identityHashCode(this)));
- sb.append(" watchinguid=");
- UserHandle.formatUid(sb, mWatchingUid);
- sb.append(" from uid=");
- UserHandle.formatUid(sb, mCallingUid);
- sb.append(" pid=");
- sb.append(mCallingPid);
- sb.append('}');
- return sb.toString();
- }
-
- void destroy() {
- mCallback.asBinder().unlinkToDeath(this, 0);
- }
-
- @Override
- public void binderDied() {
- stopWatchingNoted(mCallback);
- }
- }
-
- /**
- * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
- */
- static void onClientDeath(@NonNull AttributedOp attributedOp,
- @NonNull IBinder clientId) {
- attributedOp.onClientDeath(clientId);
- }
-
-
/**
* Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
* so that we do not log the same operation twice between instances
@@ -925,20 +233,12 @@
}
public AppOpsService(File storagePath, Handler handler, Context context) {
- mContext = context;
+ this(handler, context, new AppOpsServiceImpl(storagePath, handler, context));
+ }
- for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
- int switchCode = AppOpsManager.opToSwitch(switchedCode);
- mSwitchedOps.put(switchCode,
- ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
- }
- mAppOpsServiceInterface =
- new LegacyAppOpsServiceInterfaceImpl(this, this, handler, context, mSwitchedOps);
- mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
- mAppOpsServiceInterface);
-
- LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
- mFile = new AtomicFile(storagePath, "appops");
+ @VisibleForTesting
+ public AppOpsService(Handler handler, Context context,
+ AppOpsServiceInterface appOpsServiceInterface) {
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
"noteOpStackTraces.json");
@@ -946,185 +246,25 @@
} else {
mNoteOpCallerStacktracesFile = null;
}
+
+ mAppOpsService = appOpsServiceInterface;
+ mContext = context;
mHandler = handler;
- mConstants = new Constants(mHandler);
- readState();
}
+ /**
+ * Publishes binder and local service.
+ */
public void publish() {
ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
LocalServices.addService(AppOpsManagerInternal.class, mAppOpsManagerInternal);
}
- /** Handler for work when packages are removed or updated */
- private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
- int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
-
- if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
- synchronized (AppOpsService.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
- Ops removedOps = uidState.pkgOps.remove(pkgName);
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
- } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
- AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
- if (pkg == null) {
- return;
- }
-
- ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
- ArraySet<String> attributionTags = new ArraySet<>();
- attributionTags.add(null);
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
- attributionTags.add(attribution.getTag());
-
- int numInheritFrom = attribution.getInheritFrom().size();
- for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
- inheritFromNum++) {
- dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
- attribution.getTag());
- }
- }
- }
-
- synchronized (AppOpsService.this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- return;
- }
-
- // Reset cached package properties to re-initialize when needed
- ops.bypass = null;
- ops.knownAttributionTags.clear();
-
- // Merge data collected for removed attributions into their successor
- // attributions
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = numAttributions - 1; attributionNum >= 0;
- attributionNum--) {
- String attributionTag = op.mAttributions.keyAt(attributionNum);
-
- if (attributionTags.contains(attributionTag)) {
- // attribution still exist after upgrade
- continue;
- }
-
- String newAttributionTag = dstAttributionTags.get(attributionTag);
-
- AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
- newAttributionTag);
- newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
- op.mAttributions.removeAt(attributionNum);
-
- scheduleFastWriteLocked();
- }
- }
- }
- }
- }
- };
-
+ /**
+ * Finishes boot sequence.
+ */
public void systemReady() {
- mConstants.startMonitoring(mContext.getContentResolver());
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
-
- IntentFilter packageUpdateFilter = new IntentFilter();
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
- packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- packageUpdateFilter.addDataScheme("package");
-
- mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
- packageUpdateFilter, null, null);
-
- synchronized (this) {
- for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
- int uid = mUidStates.keyAt(uidNum);
- UidState uidState = mUidStates.valueAt(uidNum);
-
- String[] pkgsInUid = getPackagesForUid(uidState.uid);
- if (ArrayUtils.isEmpty(pkgsInUid)) {
- uidState.clear();
- mUidStates.removeAt(uidNum);
- scheduleFastWriteLocked();
- continue;
- }
-
- ArrayMap<String, Ops> pkgs = uidState.pkgOps;
- if (pkgs == null) {
- continue;
- }
-
- int numPkgs = pkgs.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- String pkg = pkgs.keyAt(pkgNum);
-
- String action;
- if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
- } else {
- action = Intent.ACTION_PACKAGE_REPLACED;
- }
-
- SystemServerInitThreadPool.submit(
- () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
- .setData(Uri.fromParts("package", pkg, null))
- .putExtra(Intent.EXTRA_UID, uid)),
- "Update app-ops uidState in case package " + pkg + " changed");
- }
- }
- }
-
- final IntentFilter packageSuspendFilter = new IntentFilter();
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
- packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- mContext.registerReceiverAsUser(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
- final String[] changedPkgs = intent.getStringArrayExtra(
- Intent.EXTRA_CHANGED_PACKAGE_LIST);
- for (int code : OPS_RESTRICTED_ON_SUSPEND) {
- ArraySet<OnOpModeChangedListener> onModeChangedListeners;
- synchronized (AppOpsService.this) {
- onModeChangedListeners =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (onModeChangedListeners == null) {
- continue;
- }
- }
- for (int i = 0; i < changedUids.length; i++) {
- final int changedUid = changedUids[i];
- final String changedPkg = changedPkgs[i];
- // We trust packagemanager to insert matching uid and packageNames in the
- // extras
- notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
- }
- }
- }
- }, UserHandle.ALL, packageSuspendFilter, null, null);
+ mAppOpsService.systemReady();
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
@@ -1132,9 +272,8 @@
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final Uri data = intent.getData();
- final String packageName = data.getSchemeSpecificPart();
+ final String packageName = intent.getData().getSchemeSpecificPart();
PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
PackageManager.GET_PERMISSIONS, Process.myUid(), mContext.getUserId());
if (isSamplingTarget(pi)) {
@@ -1169,8 +308,6 @@
}
}
});
-
- mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
}
/**
@@ -1185,132 +322,18 @@
mCheckOpsDelegateDispatcher = new CheckOpsDelegateDispatcher(policy, delegate);
}
+ /**
+ * Notify when a package is removed
+ */
public void packageRemoved(int uid, String packageName) {
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- return;
- }
-
- Ops removedOps = null;
-
- // Remove any package state if such.
- if (uidState.pkgOps != null) {
- removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- }
-
- // If we just nuked the last package state check if the UID is valid.
- if (removedOps != null && uidState.pkgOps.isEmpty()
- && getPackagesForUid(uid).length <= 0) {
- uidState.clear();
- mUidStates.remove(uid);
- }
-
- if (removedOps != null) {
- scheduleFastWriteLocked();
-
- final int numOps = removedOps.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- final Op op = removedOps.valueAt(opNum);
-
- final int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
-
- while (attributedOp.isRunning()) {
- attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
- }
- while (attributedOp.isPaused()) {
- attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
- }
- }
- }
- }
- }
-
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
- mHistoricalRegistry, uid, packageName));
+ mAppOpsService.packageRemoved(uid, packageName);
}
+ /**
+ * Notify when a uid is removed.
+ */
public void uidRemoved(int uid) {
- synchronized (this) {
- if (mUidStates.indexOfKey(uid) >= 0) {
- mUidStates.get(uid).clear();
- mUidStates.remove(uid);
- scheduleFastWriteLocked();
- }
- }
- }
-
- // The callback method from ForegroundPolicyInterface
- private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, true);
-
- if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
- for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
- if (!uidState.foregroundOps.valueAt(fgi)) {
- continue;
- }
- final int code = uidState.foregroundOps.keyAt(fgi);
-
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
- && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChangedForAllPkgsInUid,
- this, code, uidState.uid, true, null));
- } else if (uidState.pkgOps != null) {
- final ArraySet<OnOpModeChangedListener> listenerSet =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (listenerSet != null) {
- for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
- final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
- if ((listener.getFlags()
- & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
- || !listener.isWatchingUid(uidState.uid)) {
- continue;
- }
- for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
- final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
- if (op == null) {
- continue;
- }
- if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, listenerSet.valueAt(cbi), code, uidState.uid,
- uidState.pkgOps.keyAt(pkgi)));
- }
- }
- }
- }
- }
- }
- }
-
- if (uidState != null && uidState.pkgOps != null) {
- int numPkgs = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
-
- int numOps = ops.size();
- for (int opNum = 0; opNum < numOps; opNum++) {
- Op op = ops.valueAt(opNum);
-
- int numAttributions = op.mAttributions.size();
- for (int attributionNum = 0; attributionNum < numAttributions;
- attributionNum++) {
- AttributedOp attributedOp = op.mAttributions.valueAt(
- attributionNum);
-
- attributedOp.onUidStateChanged(state);
- }
- }
- }
- }
- }
+ mAppOpsService.uidRemoved(uid);
}
/**
@@ -1318,542 +341,60 @@
*/
public void updateUidProcState(int uid, int procState,
@ActivityManager.ProcessCapability int capability) {
- synchronized (this) {
- getUidStateTracker().updateUidProcState(uid, procState, capability);
- if (!mUidStates.contains(uid)) {
- UidState uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- onUidStateChanged(uid,
- AppOpsUidStateTracker.processStateToUidState(procState), false);
- }
- }
+ mAppOpsService.updateUidProcState(uid, procState, capability);
}
+ /**
+ * Initiates shutdown.
+ */
public void shutdown() {
- Slog.w(TAG, "Writing app ops before shutdown...");
- boolean doWrite = false;
- synchronized (this) {
- if (mWriteScheduled) {
- mWriteScheduled = false;
- mFastWriteScheduled = false;
- mHandler.removeCallbacks(mWriteRunner);
- doWrite = true;
- }
- }
- if (doWrite) {
- writeState();
- }
+ mAppOpsService.shutdown();
+
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
-
- mHistoricalRegistry.shutdown();
- }
-
- private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int j=0; j<pkgOps.size(); j++) {
- Op curOp = pkgOps.valueAt(j);
- resOps.add(getOpEntryForResult(curOp));
- }
- } else {
- for (int j=0; j<ops.length; j++) {
- Op curOp = pkgOps.get(ops[j]);
- if (curOp != null) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(getOpEntryForResult(curOp));
- }
- }
- }
- return resOps;
- }
-
- @Nullable
- private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
- @Nullable int[] ops) {
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes == null) {
- return null;
- }
-
- int opModeCount = opModes.size();
- if (opModeCount == 0) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = null;
- if (ops == null) {
- resOps = new ArrayList<>();
- for (int i = 0; i < opModeCount; i++) {
- int code = opModes.keyAt(i);
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- } else {
- for (int j=0; j<ops.length; j++) {
- int code = ops[j];
- if (opModes.indexOfKey(code) >= 0) {
- if (resOps == null) {
- resOps = new ArrayList<>();
- }
- resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
- }
- }
- }
- return resOps;
- }
-
- private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
- return op.createEntryLocked();
}
@Override
public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
- final int callingUid = Binder.getCallingUid();
- final boolean hasAllPackageAccess = mContext.checkPermission(
- Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
- Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
- ArrayList<AppOpsManager.PackageOps> res = null;
- synchronized (this) {
- final int uidStateCount = mUidStates.size();
- for (int i = 0; i < uidStateCount; i++) {
- UidState uidState = mUidStates.valueAt(i);
- if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
- continue;
- }
- ArrayMap<String, Ops> packages = uidState.pkgOps;
- final int packageCount = packages.size();
- for (int j = 0; j < packageCount; j++) {
- Ops pkgOps = packages.valueAt(j);
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps != null) {
- if (res == null) {
- res = new ArrayList<>();
- }
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- // Caller can always see their packages and with a permission all.
- if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
- res.add(resPackage);
- }
- }
- }
- }
- }
- return res;
+ return mAppOpsService.getPackagesForOps(ops);
}
@Override
public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
int[] ops) {
- enforceGetAppOpsStatsPermissionIfNeeded(uid,packageName);
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return Collections.emptyList();
- }
- synchronized (this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
- /* edit */ false);
- if (pkgOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- pkgOps.packageName, pkgOps.uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
- final int callingUid = Binder.getCallingUid();
- // We get to access everything
- if (callingUid == Process.myPid()) {
- return;
- }
- // Apps can access their own data
- if (uid == callingUid && packageName != null
- && checkPackage(uid, packageName) == MODE_ALLOWED) {
- return;
- }
- // Otherwise, you need a permission...
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), callingUid, null);
- }
-
- /**
- * Verify that historical appop request arguments are valid.
- */
- private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
- String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
- long endTimeMillis, int flags) {
- if ((filter & FILTER_BY_UID) != 0) {
- Preconditions.checkArgument(uid != Process.INVALID_UID);
- } else {
- Preconditions.checkArgument(uid == Process.INVALID_UID);
- }
-
- if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
- Objects.requireNonNull(packageName);
- } else {
- Preconditions.checkArgument(packageName == null);
- }
-
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
- Preconditions.checkArgument(attributionTag == null);
- }
-
- if ((filter & FILTER_BY_OP_NAMES) != 0) {
- Objects.requireNonNull(opNames);
- } else {
- Preconditions.checkArgument(opNames == null);
- }
-
- Preconditions.checkFlagsArgument(filter,
- FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
- | FILTER_BY_OP_NAMES);
- Preconditions.checkArgumentNonnegative(beginTimeMillis);
- Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
- Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ return mAppOpsService.getOpsForPackage(uid, packageName, ops);
}
@Override
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- PackageManager pm = mContext.getPackageManager();
-
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
- ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
- boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
- if (!isSelfRequest) {
- boolean isCallerInstrumented =
- ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
- boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
- boolean isCallerPermissionController;
- try {
- isCallerPermissionController = pm.getPackageUidAsUser(
- mContext.getPackageManager().getPermissionControllerPackageName(), 0,
- UserHandle.getUserId(Binder.getCallingUid()))
- == Binder.getCallingUid();
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- return;
- }
-
- boolean doesCallerHavePermission = mContext.checkPermission(
- android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid())
- == PackageManager.PERMISSION_GRANTED;
-
- if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
- && !doesCallerHavePermission) {
- mHandler.post(() -> callback.sendResult(new Bundle()));
- return;
- }
-
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
- }
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
+ mAppOpsService.getHistoricalOps(uid, packageName, attributionTag, opNames,
+ dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
- ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
- beginTimeMillis, endTimeMillis, flags);
- Objects.requireNonNull(callback, "callback cannot be null");
-
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
-
- final String[] opNamesArray = (opNames != null)
- ? opNames.toArray(new String[opNames.size()]) : null;
-
- Set<String> attributionChainExemptPackages = null;
- if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
- attributionChainExemptPackages =
- PermissionManager.getIndicatorExemptedPackages(mContext);
- }
-
- final String[] chainExemptPkgArray = attributionChainExemptPackages != null
- ? attributionChainExemptPackages.toArray(
- new String[attributionChainExemptPackages.size()]) : null;
-
- // Must not hold the appops lock
- mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
- mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
- filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
- callback).recycleOnUse());
+ mAppOpsService.getHistoricalOpsFromDiskRaw(uid, packageName, attributionTag,
+ opNames, dataType, filter, beginTimeMillis, endTimeMillis, flags, callback);
}
@Override
public void reloadNonHistoricalState() {
- mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
- Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
- writeState();
- readState();
+ mAppOpsService.reloadNonHistoricalState();
}
@Override
public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
- mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- return null;
- }
- ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
- if (resOps == null) {
- return null;
- }
- ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
- AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
- null, uidState.uid, resOps);
- res.add(resPackage);
- return res;
- }
- }
-
- private void pruneOpLocked(Op op, int uid, String packageName) {
- op.removeAttributionsWithNoTime();
-
- if (op.mAttributions.isEmpty()) {
- Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
- if (ops != null) {
- ops.remove(op.op);
- op.setMode(AppOpsManager.opToDefaultMode(op.op));
- if (ops.size() <= 0) {
- UidState uidState = ops.uidState;
- ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
- if (pkgOps != null) {
- pkgOps.remove(ops.packageName);
- mAppOpsServiceInterface.removePackage(ops.packageName,
- UserHandle.getUserId(uidState.uid));
- if (pkgOps.isEmpty()) {
- uidState.pkgOps = null;
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uid);
- }
- }
- }
- }
- }
- }
-
- private void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
- if (callingPid == Process.myPid()) {
- return;
- }
- final int callingUser = UserHandle.getUserId(callingUid);
- synchronized (this) {
- if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
- if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
- // Profile owners are allowed to change modes but only for apps
- // within their user.
- return;
- }
- }
- }
- mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
+ return mAppOpsService.getUidOps(uid, ops);
}
@Override
public void setUidMode(int code, int uid, int mode) {
- setUidMode(code, uid, mode, null);
- }
-
- private void setUidMode(int code, int uid, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- if (DEBUG) {
- Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
- + " by uid " + Binder.getCallingUid());
- }
-
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- code = AppOpsManager.opToSwitch(code);
-
- if (permissionPolicyCallback == null) {
- updatePermissionRevokedCompat(uid, code, mode);
- }
-
- int previousMode;
- synchronized (this) {
- final int defaultMode = AppOpsManager.opToDefaultMode(code);
-
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState == null) {
- if (mode == defaultMode) {
- return;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
- if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- previousMode = uidState.getUidMode(code);
- } else {
- // doesn't look right but is legacy behavior.
- previousMode = MODE_DEFAULT;
- }
-
- if (!uidState.setUidMode(code, mode)) {
- return;
- }
- uidState.evalForegroundOps();
- if (mode != MODE_ERRORED && mode != previousMode) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
-
- notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
- notifyOpChangedSync(code, uid, null, mode, previousMode);
- }
-
- /**
- * Notify that an op changed for all packages in an uid.
- *
- * @param code The op that changed
- * @param uid The uid the op was changed for
- * @param onlyForeground Only notify watchers that watch for foreground changes
- */
- private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
- @Nullable IAppOpsCallback callbackToIgnore) {
- ModeCallback listenerToIgnore = callbackToIgnore != null
- ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
- mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
- listenerToIgnore);
- }
-
- private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
- PackageManager packageManager = mContext.getPackageManager();
- if (packageManager == null) {
- // This can only happen during early boot. At this time the permission state and appop
- // state are in sync
- return;
- }
-
- String[] packageNames = packageManager.getPackagesForUid(uid);
- if (ArrayUtils.isEmpty(packageNames)) {
- return;
- }
- String packageName = packageNames[0];
-
- int[] ops = mSwitchedOps.get(switchCode);
- for (int code : ops) {
- String permissionName = AppOpsManager.opToPermission(code);
- if (permissionName == null) {
- continue;
- }
-
- if (packageManager.checkPermission(permissionName, packageName)
- != PackageManager.PERMISSION_GRANTED) {
- continue;
- }
-
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
- } catch (PackageManager.NameNotFoundException e) {
- e.printStackTrace();
- continue;
- }
-
- if (!permissionInfo.isRuntime()) {
- continue;
- }
-
- boolean supportsRuntimePermissions = getPackageManagerInternal()
- .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
-
- UserHandle user = UserHandle.getUserHandleForUid(uid);
- boolean isRevokedCompat;
- if (permissionInfo.backgroundPermission != null) {
- if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
- == PackageManager.PERMISSION_GRANTED) {
- boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
-
- if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission="
- + permissionInfo.backgroundPermission);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
- packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
- isBackgroundRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
- && mode != AppOpsManager.MODE_FOREGROUND;
- } else {
- isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
- }
-
- if (isRevokedCompat && supportsRuntimePermissions) {
- Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
- + " permission state, this is discouraged and you should revoke the"
- + " runtime permission instead: uid=" + uid + ", switchCode="
- + switchCode + ", mode=" + mode + ", permission=" + permissionName);
- }
-
- final long identity = Binder.clearCallingIdentity();
- try {
- packageManager.updatePermissionFlags(permissionName, packageName,
- PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
- ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
- int previousMode) {
- final StorageManagerInternal storageManagerInternal =
- LocalServices.getService(StorageManagerInternal.class);
- if (storageManagerInternal != null) {
- storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
- }
+ mAppOpsService.setUidMode(code, uid, mode, null);
}
/**
@@ -1866,309 +407,12 @@
*/
@Override
public void setMode(int code, int uid, @NonNull String packageName, int mode) {
- setMode(code, uid, packageName, mode, null);
- }
-
- private void setMode(int code, int uid, @NonNull String packageName, int mode,
- @Nullable IAppOpsCallback permissionPolicyCallback) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- ArraySet<OnOpModeChangedListener> repCbs = null;
- code = AppOpsManager.opToSwitch(code);
-
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot setMode", e);
- return;
- }
-
- int previousMode = MODE_DEFAULT;
- synchronized (this) {
- UidState uidState = getUidStateLocked(uid, false);
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
- if (op != null) {
- if (op.getMode() != mode) {
- previousMode = op.getMode();
- op.setMode(mode);
-
- if (uidState != null) {
- uidState.evalForegroundOps();
- }
- ArraySet<OnOpModeChangedListener> cbs =
- mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
- if (cbs != null) {
- if (repCbs == null) {
- repCbs = new ArraySet<>();
- }
- repCbs.addAll(cbs);
- }
- if (repCbs != null && permissionPolicyCallback != null) {
- repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
- }
- if (mode == AppOpsManager.opToDefaultMode(op.op)) {
- // If going into the default mode, prune this op
- // if there is nothing else interesting in it.
- pruneOpLocked(op, uid, packageName);
- }
- scheduleFastWriteLocked();
- if (mode != MODE_ERRORED) {
- updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
- }
- }
- }
- }
- if (repCbs != null) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, repCbs, code, uid, packageName));
- }
-
- notifyOpChangedSync(code, uid, packageName, mode, previousMode);
- }
-
- private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
- int uid, String packageName) {
- for (int i = 0; i < callbacks.size(); i++) {
- final OnOpModeChangedListener callback = callbacks.valueAt(i);
- notifyOpChanged(callback, code, uid, packageName);
- }
- }
-
- private void notifyOpChanged(OnOpModeChangedListener callback, int code,
- int uid, String packageName) {
- mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
- }
-
- private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
- int op, int uid, String packageName, int previousMode) {
- boolean duplicate = false;
- if (reports == null) {
- reports = new ArrayList<>();
- } else {
- final int reportCount = reports.size();
- for (int j = 0; j < reportCount; j++) {
- ChangeRec report = reports.get(j);
- if (report.op == op && report.pkg.equals(packageName)) {
- duplicate = true;
- break;
- }
- }
- }
- if (!duplicate) {
- reports.add(new ChangeRec(op, uid, packageName, previousMode));
- }
-
- return reports;
- }
-
- private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
- int op, int uid, String packageName, int previousMode,
- ArraySet<OnOpModeChangedListener> cbs) {
- if (cbs == null) {
- return callbacks;
- }
- if (callbacks == null) {
- callbacks = new HashMap<>();
- }
- final int N = cbs.size();
- for (int i=0; i<N; i++) {
- OnOpModeChangedListener cb = cbs.valueAt(i);
- ArrayList<ChangeRec> reports = callbacks.get(cb);
- ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
- if (changed != reports) {
- callbacks.put(cb, changed);
- }
- }
- return callbacks;
- }
-
- static final class ChangeRec {
- final int op;
- final int uid;
- final String pkg;
- final int previous_mode;
-
- ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
- op = _op;
- uid = _uid;
- pkg = _pkg;
- previous_mode = _previous_mode;
- }
+ mAppOpsService.setMode(code, uid, packageName, mode, null);
}
@Override
public void resetAllModes(int reqUserId, String reqPackageName) {
- final int callingPid = Binder.getCallingPid();
- final int callingUid = Binder.getCallingUid();
- reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
- true, true, "resetAllModes", null);
-
- int reqUid = -1;
- if (reqPackageName != null) {
- try {
- reqUid = AppGlobals.getPackageManager().getPackageUid(
- reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
-
- enforceManageAppOpsModes(callingPid, callingUid, reqUid);
-
- HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
- ArrayList<ChangeRec> allChanges = new ArrayList<>();
- synchronized (this) {
- boolean changed = false;
- for (int i = mUidStates.size() - 1; i >= 0; i--) {
- UidState uidState = mUidStates.valueAt(i);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
- final int uidOpCount = opModes.size();
- for (int j = uidOpCount - 1; j >= 0; j--) {
- final int code = opModes.keyAt(j);
- if (AppOpsManager.opAllowsReset(code)) {
- int previousMode = opModes.valueAt(j);
- uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
- for (String packageName : getPackagesForUid(uidState.uid)) {
- callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(code));
- callbacks = addCallbacks(callbacks, code, uidState.uid, packageName,
- previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, code, uidState.uid,
- packageName, previousMode);
- }
- }
- }
- }
-
- if (uidState.pkgOps == null) {
- continue;
- }
-
- if (reqUserId != UserHandle.USER_ALL
- && reqUserId != UserHandle.getUserId(uidState.uid)) {
- // Skip any ops for a different user
- continue;
- }
-
- Map<String, Ops> packages = uidState.pkgOps;
- Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
- boolean uidChanged = false;
- while (it.hasNext()) {
- Map.Entry<String, Ops> ent = it.next();
- String packageName = ent.getKey();
- if (reqPackageName != null && !reqPackageName.equals(packageName)) {
- // Skip any ops for a different package
- continue;
- }
- Ops pkgOps = ent.getValue();
- for (int j=pkgOps.size()-1; j>=0; j--) {
- Op curOp = pkgOps.valueAt(j);
- if (shouldDeferResetOpToDpm(curOp.op)) {
- deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
- continue;
- }
- if (AppOpsManager.opAllowsReset(curOp.op)
- && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
- int previousMode = curOp.getMode();
- curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
- changed = true;
- uidChanged = true;
- final int uid = curOp.uidState.uid;
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode,
- mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
- callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
- previousMode, mAppOpsServiceInterface
- .getPackageModeChangedListeners(packageName));
-
- allChanges = addChange(allChanges, curOp.op, uid, packageName,
- previousMode);
- curOp.removeAttributionsWithNoTime();
- if (curOp.mAttributions.isEmpty()) {
- pkgOps.removeAt(j);
- }
- }
- }
- if (pkgOps.size() == 0) {
- it.remove();
- mAppOpsServiceInterface.removePackage(packageName,
- UserHandle.getUserId(uidState.uid));
- }
- }
- if (uidState.isDefault()) {
- uidState.clear();
- mUidStates.remove(uidState.uid);
- }
- if (uidChanged) {
- uidState.evalForegroundOps();
- }
- }
-
- if (changed) {
- scheduleFastWriteLocked();
- }
- }
- if (callbacks != null) {
- for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
- : callbacks.entrySet()) {
- OnOpModeChangedListener cb = ent.getKey();
- ArrayList<ChangeRec> reports = ent.getValue();
- for (int i=0; i<reports.size(); i++) {
- ChangeRec rep = reports.get(i);
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChanged,
- this, cb, rep.op, rep.uid, rep.pkg));
- }
- }
- }
-
- int numChanges = allChanges.size();
- for (int i = 0; i < numChanges; i++) {
- ChangeRec change = allChanges.get(i);
- notifyOpChangedSync(change.op, change.uid, change.pkg,
- AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
- }
- }
-
- private boolean shouldDeferResetOpToDpm(int op) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- return dpmi != null && dpmi.supportsResetOp(op);
- }
-
- /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
- private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
- // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
- // pre-grants to a role-based mechanism or another general-purpose mechanism.
- dpmi.resetOp(op, packageName, userId);
- }
-
- private void evalAllForegroundOpsLocked() {
- for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
- final UidState uidState = mUidStates.valueAt(uidi);
- if (uidState.foregroundOps != null) {
- uidState.evalForegroundOps();
- }
- }
+ mAppOpsService.resetAllModes(reqUserId, reqPackageName);
}
@Override
@@ -2179,66 +423,17 @@
@Override
public void startWatchingModeWithFlags(int op, String packageName, int flags,
IAppOpsCallback callback) {
- int watchedUid = -1;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- // TODO: should have a privileged permission to protect this.
- // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
- // the USAGE_STATS permission since this can provide information about when an
- // app is in the foreground?
- Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
- AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
- if (callback == null) {
- return;
- }
- final boolean mayWatchPackageName = packageName != null
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
- synchronized (this) {
- int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
-
- int notifiedOps;
- if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
- if (op == OP_NONE) {
- notifiedOps = ALL_OPS;
- } else {
- notifiedOps = op;
- }
- } else {
- notifiedOps = switchOp;
- }
-
- ModeCallback cb = mModeWatchers.get(callback.asBinder());
- if (cb == null) {
- cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
- callingPid);
- mModeWatchers.put(callback.asBinder(), cb);
- }
- if (switchOp != AppOpsManager.OP_NONE) {
- mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
- }
- if (mayWatchPackageName) {
- mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
- }
- evalAllForegroundOpsLocked();
- }
+ mAppOpsService.startWatchingModeWithFlags(op, packageName, flags, callback);
}
@Override
public void stopWatchingMode(IAppOpsCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- ModeCallback cb = mModeWatchers.remove(callback.asBinder());
- if (cb != null) {
- cb.unlinkToDeath();
- mAppOpsServiceInterface.removeListener(cb);
- }
-
- evalAllForegroundOpsLocked();
- }
+ mAppOpsService.stopWatchingMode(callback);
}
+ /**
+ * @return the current {@link CheckOpsDelegate}.
+ */
public CheckOpsDelegate getAppOpsServiceDelegate() {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher dispatcher = mCheckOpsDelegateDispatcher;
@@ -2246,6 +441,9 @@
}
}
+ /**
+ * Sets the appops {@link CheckOpsDelegate}
+ */
public void setAppOpsServiceDelegate(CheckOpsDelegate delegate) {
synchronized (AppOpsService.this) {
final CheckOpsDelegateDispatcher oldDispatcher = mCheckOpsDelegateDispatcher;
@@ -2269,58 +467,7 @@
private int checkOperationImpl(int code, int uid, String packageName,
@Nullable String attributionTag, boolean raw) {
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return AppOpsManager.opToDefaultMode(code);
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return AppOpsManager.MODE_IGNORED;
- }
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
- }
-
- /**
- * Get the mode of an app-op.
- *
- * @param code The code of the op
- * @param uid The uid of the package the op belongs to
- * @param packageName The package the op belongs to
- * @param raw If the raw state of eval-ed state should be checked.
- *
- * @return The mode of the op
- */
- private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean raw) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, null);
- } catch (SecurityException e) {
- Slog.e(TAG, "checkOperation", e);
- return AppOpsManager.opToDefaultMode(code);
- }
-
- if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
- return AppOpsManager.MODE_IGNORED;
- }
- synchronized (this) {
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
- return AppOpsManager.MODE_IGNORED;
- }
- code = AppOpsManager.opToSwitch(code);
- UidState uidState = getUidStateLocked(uid, false);
- if (uidState != null
- && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
- final int rawMode = uidState.getUidMode(code);
- return raw ? rawMode : uidState.evalMode(code, rawMode);
- }
- Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
- if (op == null) {
- return AppOpsManager.opToDefaultMode(code);
- }
- return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
- }
+ return mAppOpsService.checkOperation(code, uid, packageName, attributionTag, raw);
}
@Override
@@ -2340,7 +487,8 @@
@Override
public void setAudioRestriction(int code, int usage, int uid, int mode,
String[] exceptionPackages) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), uid);
verifyIncomingUid(uid);
verifyIncomingOp(code);
@@ -2348,58 +496,35 @@
code, usage, uid, mode, exceptionPackages);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService, code,
+ UID_ANY));
}
@Override
public void setCameraAudioRestriction(@CAMERA_AUDIO_RESTRICTION int mode) {
- enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), -1);
+ mAppOpsService.enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), -1);
mAudioRestrictionManager.setCameraAudioRestriction(mode);
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
AppOpsManager.OP_PLAY_AUDIO, UID_ANY));
mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this,
+ AppOpsServiceInterface::notifyWatchersOfChange, mAppOpsService,
AppOpsManager.OP_VIBRATE, UID_ANY));
}
@Override
public int checkPackage(int uid, String packageName) {
- Objects.requireNonNull(packageName);
- try {
- verifyAndGetBypass(uid, packageName, null);
- // When the caller is the system, it's possible that the packageName is the special
- // one (e.g., "root") which isn't actually existed.
- if (resolveUid(packageName) == uid
- || (isPackageExisted(packageName)
- && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
- return AppOpsManager.MODE_ALLOWED;
- }
- return AppOpsManager.MODE_ERRORED;
- } catch (SecurityException ignored) {
- return AppOpsManager.MODE_ERRORED;
- }
+ return mAppOpsService.checkPackage(uid, packageName);
}
private boolean isPackageExisted(String packageName) {
return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
}
- /**
- * This method will check with PackageManager to determine if the package provided should
- * be visible to the {@link Binder#getCallingUid()}.
- *
- * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
- */
- private boolean filterAppAccessUnlocked(String packageName, int userId) {
- final int callingUid = Binder.getCallingUid();
- return LocalServices.getService(PackageManagerInternal.class)
- .filterAppAccess(packageName, callingUid, userId);
- }
-
@Override
public SyncNotedAppOp noteProxyOperation(int code, AttributionSource attributionSource,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
@@ -2445,13 +570,20 @@
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyReturn = noteOperationUnchecked(code, proxyUid,
+ final int proxyReturn = mAppOpsService.noteOperationUnchecked(code, proxyUid,
resolveProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, !isProxyTrusted, "proxy " + message, shouldCollectMessage);
- if (proxyReturn.getOpMode() != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(proxyReturn.getOpMode(), code, proxiedAttributionTag,
+ proxyFlags);
+ if (proxyReturn != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(proxyReturn, code, proxiedAttributionTag,
proxiedPackageName);
}
+ if (shouldCollectAsyncNotedOp) {
+ boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+ resolveProxyPackageName, proxyAttributionTag, null);
+ collectAsyncNotedOp(proxyUid, resolveProxyPackageName, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+ message, shouldCollectMessage);
+ }
}
String resolveProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -2463,9 +595,32 @@
final int proxiedFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXIED
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED;
- return noteOperationUnchecked(code, proxiedUid, resolveProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolveProxyPackageName, proxyAttributionTag,
- proxiedFlags, shouldCollectAsyncNotedOp, message, shouldCollectMessage);
+ final int result = mAppOpsService.noteOperationUnchecked(code, proxiedUid,
+ resolveProxiedPackageName, proxiedAttributionTag, proxyUid, resolveProxyPackageName,
+ proxyAttributionTag, proxiedFlags);
+
+ boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+ resolveProxiedPackageName, proxiedAttributionTag, resolveProxyPackageName);
+ if (shouldCollectAsyncNotedOp && result == AppOpsManager.MODE_ALLOWED) {
+ collectAsyncNotedOp(proxiedUid, resolveProxiedPackageName, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null, proxiedFlags,
+ message, shouldCollectMessage);
+ }
+
+
+ return new SyncNotedAppOp(result, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolveProxiedPackageName);
+ }
+
+ private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
+ if (attributionSource.getUid() != Binder.getCallingUid()
+ && attributionSource.isTrusted(mContext)) {
+ return true;
+ }
+ return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null)
+ == PackageManager.PERMISSION_GRANTED;
}
@Override
@@ -2479,258 +634,58 @@
private SyncNotedAppOp noteOperationImpl(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, boolean shouldCollectAsyncNotedOp,
@Nullable String message, boolean shouldCollectMessage) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ int result = mAppOpsService.noteOperation(code, uid, packageName,
+ attributionTag, message);
+
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage);
- }
- private SyncNotedAppOp noteOperationUnchecked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, int proxyUid, String proxyPackageName,
- @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- boolean wasNull = attributionTag == null;
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "noteOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
+ boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+ resolvedPackageName, attributionTag, null);
+
+ if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+ collectAsyncNotedOp(uid, resolvedPackageName, code,
+ isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+ message, shouldCollectMessage);
}
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + "flags: " +
- AppOpsManager.flagsToString(flags));
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- if (attributedOp.isRunning()) {
- Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
- + code + " startTime of in progress event="
- + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
- }
-
- final int switchCode = AppOpsManager.opToSwitch(code);
- final UidState uidState = ops.uidState;
- if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_IGNORED);
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
- }
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (uidMode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- uidMode);
- return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (DEBUG) Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- mode);
- return new SyncNotedAppOp(mode, code, attributionTag, packageName);
- }
- }
- if (DEBUG) {
- Slog.d(TAG,
- "noteOperation: allowing code " + code + " uid " + uid + " package "
- + packageName + (attributionTag == null ? ""
- : "." + attributionTag) + " flags: "
- + AppOpsManager.flagsToString(flags));
- }
- scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- AppOpsManager.MODE_ALLOWED);
- attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
- uidState.getState(),
- flags);
-
- if (shouldCollectAsyncNotedOp) {
- collectAsyncNotedOp(uid, packageName, code, attributionTag, flags, message,
- shouldCollectMessage);
- }
-
- return new SyncNotedAppOp(AppOpsManager.MODE_ALLOWED, code, attributionTag,
- packageName);
- }
+ return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+ resolvedPackageName);
}
// TODO moltmann: Allow watching for attribution ops
@Override
public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- if (ops != null) {
- Preconditions.checkArrayElementsInRange(ops, 0,
- AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
- }
- if (callback == null) {
- return;
- }
- synchronized (this) {
- SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mActiveWatchers.put(callback.asBinder(), callbacks);
- }
- final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, activeCallback);
- }
- }
+ mAppOpsService.startWatchingActive(ops, callback);
}
@Override
public void stopWatchingActive(IAppOpsActiveCallback callback) {
- if (callback == null) {
- return;
- }
- synchronized (this) {
- final SparseArray<ActiveCallback> activeCallbacks =
- mActiveWatchers.remove(callback.asBinder());
- if (activeCallbacks == null) {
- return;
- }
- final int callbackCount = activeCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- activeCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingActive(callback);
}
@Override
public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
-
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mStartedWatchers.put(callback.asBinder(), callbacks);
- }
-
- final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, startedCallback);
- }
- }
+ mAppOpsService.startWatchingStarted(ops, callback);
}
@Override
public void stopWatchingStarted(IAppOpsStartedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
-
- synchronized (this) {
- final SparseArray<StartedCallback> startedCallbacks =
- mStartedWatchers.remove(callback.asBinder());
- if (startedCallbacks == null) {
- return;
- }
-
- final int callbackCount = startedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- startedCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingStarted(callback);
}
@Override
public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
- int watchedUid = Process.INVALID_UID;
- final int callingUid = Binder.getCallingUid();
- final int callingPid = Binder.getCallingPid();
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- watchedUid = callingUid;
- }
- Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
- Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
- "Invalid op code in: " + Arrays.toString(ops));
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
- if (callbacks == null) {
- callbacks = new SparseArray<>();
- mNotedWatchers.put(callback.asBinder(), callbacks);
- }
- final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
- callingUid, callingPid);
- for (int op : ops) {
- callbacks.put(op, notedCallback);
- }
- }
+ mAppOpsService.startWatchingNoted(ops, callback);
}
@Override
public void stopWatchingNoted(IAppOpsNotedCallback callback) {
- Objects.requireNonNull(callback, "Callback cannot be null");
- synchronized (this) {
- final SparseArray<NotedCallback> notedCallbacks =
- mNotedWatchers.remove(callback.asBinder());
- if (notedCallbacks == null) {
- return;
- }
- final int callbackCount = notedCallbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- notedCallbacks.valueAt(i).destroy();
- }
- }
+ mAppOpsService.stopWatchingNoted(callback);
}
/**
@@ -2817,7 +772,7 @@
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2847,7 +802,7 @@
int uid = Binder.getCallingUid();
Pair<String, Integer> key = getAsyncNotedOpsKey(packageName, uid);
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
RemoteCallbackList<IAppOpsAsyncNotedCallback> callbacks = mAsyncOpWatchers.get(key);
@@ -2866,7 +821,7 @@
int uid = Binder.getCallingUid();
- verifyAndGetBypass(uid, packageName, null);
+ mAppOpsService.verifyPackage(uid, packageName);
synchronized (this) {
return mUnforwardedAsyncNotedOps.remove(getAsyncNotedOpsKey(packageName, uid));
@@ -2889,54 +844,49 @@
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @NonNull String message,
boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
int attributionChainId) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ int result = mAppOpsService.startOperation(clientId, code, uid, packageName,
+ attributionTag, startIfModeDefault, message,
+ attributionFlags, attributionChainId);
+
String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return new SyncNotedAppOp(AppOpsManager.MODE_IGNORED, code, attributionTag,
- packageName);
+
+ boolean isAttributionTagValid = mAppOpsService.isAttributionTagValid(uid,
+ resolvedPackageName, attributionTag, null);
+
+ if (shouldCollectAsyncNotedOp && result == MODE_ALLOWED) {
+ collectAsyncNotedOp(uid, resolvedPackageName, code,
+ isAttributionTagValid ? attributionTag : null, AppOpsManager.OP_FLAG_SELF,
+ message, shouldCollectMessage);
}
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
- //
- // TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
- if (result != AppOpsManager.MODE_ALLOWED) {
- return new SyncNotedAppOp(result, code, attributionTag, packageName);
- }
- }
- return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
- Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
- attributionChainId, /*dryRun*/ false);
+ return new SyncNotedAppOp(result, code, isAttributionTagValid ? attributionTag : null,
+ resolvedPackageName);
}
@Override
- public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@AttributionFlags int proxiedAttributionFlags, int attributionChainId) {
- return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code, attributionSource,
- startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skipProxyOperation, proxyAttributionFlags, proxiedAttributionFlags,
- attributionChainId);
+ return mCheckOpsDelegateDispatcher.startProxyOperation(clientId, code,
+ attributionSource, startIfModeDefault, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skipProxyOperation, proxyAttributionFlags,
+ proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private SyncNotedAppOp startProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource,
boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, String message,
boolean shouldCollectMessage, boolean skipProxyOperation, @AttributionFlags
int proxyAttributionFlags, @AttributionFlags int proxiedAttributionFlags,
int attributionChainId) {
+
final int proxyUid = attributionSource.getUid();
final String proxyPackageName = attributionSource.getPackageName();
final String proxyAttributionTag = attributionSource.getAttributionTag();
@@ -2984,147 +934,68 @@
if (!skipProxyOperation) {
// Test if the proxied operation will succeed before starting the proxy operation
- final SyncNotedAppOp testProxiedOp = startOperationUnchecked(clientId, code,
+ final int testProxiedOp = mAppOpsService.startOperationUnchecked(clientId, code,
proxiedUid, resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
proxiedAttributionFlags, attributionChainId, /*dryRun*/ true);
- if (!shouldStartForMode(testProxiedOp.getOpMode(), startIfModeDefault)) {
- return testProxiedOp;
+
+ boolean isTestProxiedAttributionTagValid =
+ mAppOpsService.isAttributionTagValid(proxiedUid, resolvedProxiedPackageName,
+ proxiedAttributionTag, resolvedProxyPackageName);
+
+ if (!shouldStartForMode(testProxiedOp, startIfModeDefault)) {
+ return new SyncNotedAppOp(testProxiedOp, code,
+ isTestProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolvedProxiedPackageName);
}
final int proxyFlags = isProxyTrusted ? AppOpsManager.OP_FLAG_TRUSTED_PROXY
: AppOpsManager.OP_FLAG_UNTRUSTED_PROXY;
- final SyncNotedAppOp proxyAppOp = startOperationUnchecked(clientId, code, proxyUid,
+ final int proxyAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxyUid,
resolvedProxyPackageName, proxyAttributionTag, Process.INVALID_UID, null, null,
- proxyFlags, startIfModeDefault, !isProxyTrusted, "proxy " + message,
- shouldCollectMessage, proxyAttributionFlags, attributionChainId,
+ proxyFlags, startIfModeDefault, proxyAttributionFlags, attributionChainId,
/*dryRun*/ false);
- if (!shouldStartForMode(proxyAppOp.getOpMode(), startIfModeDefault)) {
- return proxyAppOp;
+
+ boolean isProxyAttributionTagValid = mAppOpsService.isAttributionTagValid(proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, null);
+
+ if (!shouldStartForMode(proxyAppOp, startIfModeDefault)) {
+ return new SyncNotedAppOp(proxyAppOp, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null,
+ resolvedProxyPackageName);
+ }
+
+ if (shouldCollectAsyncNotedOp) {
+ collectAsyncNotedOp(proxyUid, resolvedProxyPackageName, code,
+ isProxyAttributionTagValid ? proxyAttributionTag : null, proxyFlags,
+ message, shouldCollectMessage);
}
}
- return startOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag, proxyUid, resolvedProxyPackageName, proxyAttributionTag,
- proxiedFlags, startIfModeDefault, shouldCollectAsyncNotedOp, message,
- shouldCollectMessage, proxiedAttributionFlags, attributionChainId,
- /*dryRun*/ false);
+ final int proxiedAppOp = mAppOpsService.startOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag, proxiedFlags, startIfModeDefault,
+ proxiedAttributionFlags, attributionChainId,/*dryRun*/ false);
+
+ boolean isProxiedAttributionTagValid = mAppOpsService.isAttributionTagValid(proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag, resolvedProxyPackageName);
+
+ if (shouldCollectAsyncNotedOp && proxiedAppOp == MODE_ALLOWED) {
+ collectAsyncNotedOp(proxyUid, resolvedProxiedPackageName, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ proxiedAttributionFlags, message, shouldCollectMessage);
+ }
+
+ return new SyncNotedAppOp(proxiedAppOp, code,
+ isProxiedAttributionTagValid ? proxiedAttributionTag : null,
+ resolvedProxiedPackageName);
}
private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
}
- private SyncNotedAppOp startOperationUnchecked(IBinder clientId, int code, int uid,
- @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
- String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
- boolean startIfModeDefault, boolean shouldCollectAsyncNotedOp, @Nullable String message,
- boolean shouldCollectMessage, @AttributionFlags int attributionFlags,
- int attributionChainId, boolean dryRun) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "startOperation", e);
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
-
- boolean isRestricted = false;
- int startType = START_TYPE_FAILED;
- synchronized (this) {
- final Ops ops = getOpsLocked(uid, packageName, attributionTag,
- pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
- if (ops == null) {
- if (!dryRun) {
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
- attributionChainId);
- }
- if (DEBUG) Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
- + " package " + packageName + " flags: "
- + AppOpsManager.flagsToString(flags));
- return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
- packageName);
- }
- final Op op = getOpLocked(ops, code, uid, true);
- final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
- final UidState uidState = ops.uidState;
- isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
- false);
- final int switchCode = AppOpsManager.opToSwitch(code);
- // If there is a non-default per UID policy (we set UID op mode only if
- // non-default) it takes over, otherwise use the per package policy.
- if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
- final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
- if (!shouldStartForMode(uidMode, startIfModeDefault)) {
- if (DEBUG) {
- Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- }
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, uidMode, startType, attributionFlags, attributionChainId);
- }
- return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
- }
- } else {
- final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
- : op;
- final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
- if (mode != AppOpsManager.MODE_ALLOWED
- && (!startIfModeDefault || mode != MODE_DEFAULT)) {
- if (DEBUG) Slog.d(TAG, "startOperation: reject #" + mode + " for code "
- + switchCode + " (" + code + ") uid " + uid + " package "
- + packageName + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- attributedOp.rejected(uidState.getState(), flags);
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
- flags, mode, startType, attributionFlags, attributionChainId);
- }
- return new SyncNotedAppOp(mode, code, attributionTag, packageName);
- }
- }
- if (DEBUG) Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
- + " package " + packageName + " restricted: " + isRestricted
- + " flags: " + AppOpsManager.flagsToString(flags));
- if (!dryRun) {
- try {
- if (isRestricted) {
- attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- } else {
- attributedOp.started(clientId, proxyUid, proxyPackageName,
- proxyAttributionTag, uidState.getState(), flags,
- attributionFlags, attributionChainId);
- startType = START_TYPE_STARTED;
- }
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
- isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
- attributionChainId);
- }
- }
-
- if (shouldCollectAsyncNotedOp && !dryRun && !isRestricted) {
- collectAsyncNotedOp(uid, packageName, code, attributionTag, AppOpsManager.OP_FLAG_SELF,
- message, shouldCollectMessage);
- }
-
- return new SyncNotedAppOp(isRestricted ? MODE_IGNORED : MODE_ALLOWED, code, attributionTag,
- packageName);
- }
-
@Override
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
@@ -3134,22 +1005,11 @@
private void finishOperationImpl(IBinder clientId, int code, int uid, String packageName,
String attributionTag) {
- verifyIncomingUid(uid);
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return;
- }
-
- String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return;
- }
-
- finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ mAppOpsService.finishOperation(clientId, code, uid, packageName, attributionTag);
}
@Override
- public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegateDispatcher.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation);
@@ -3181,8 +1041,8 @@
}
if (!skipProxyOperation) {
- finishOperationUnchecked(clientId, code, proxyUid, resolvedProxyPackageName,
- proxyAttributionTag);
+ mAppOpsService.finishOperationUnchecked(clientId, code, proxyUid,
+ resolvedProxyPackageName, proxyAttributionTag);
}
String resolvedProxiedPackageName = AppOpsManager.resolvePackageName(proxiedUid,
@@ -3191,209 +1051,12 @@
return null;
}
- finishOperationUnchecked(clientId, code, proxiedUid, resolvedProxiedPackageName,
- proxiedAttributionTag);
+ mAppOpsService.finishOperationUnchecked(clientId, code, proxiedUid,
+ resolvedProxiedPackageName, proxiedAttributionTag);
return null;
}
- private void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
- String attributionTag) {
- PackageVerificationResult pvr;
- try {
- pvr = verifyAndGetBypass(uid, packageName, attributionTag);
- if (!pvr.isAttributionTagValid) {
- attributionTag = null;
- }
- } catch (SecurityException e) {
- Slog.e(TAG, "Cannot finishOperation", e);
- return;
- }
-
- synchronized (this) {
- Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
- pvr.bypass, /* edit */ true);
- if (op == null) {
- Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp == null) {
- Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- return;
- }
-
- if (attributedOp.isRunning() || attributedOp.isPaused()) {
- attributedOp.finished(clientId);
- } else {
- Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
- + attributionTag + ") op=" + AppOpsManager.opToName(code));
- }
- }
- }
-
- void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull
- String packageName, @Nullable String attributionTag, boolean active, @AttributionFlags
- int attributionFlags, int attributionChainId) {
- ArraySet<ActiveCallback> dispatchedCallbacks = null;
- final int callbackListCount = mActiveWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
- ActiveCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpActiveChanged,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
- attributionFlags, attributionChainId));
- }
-
- private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
- int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
- boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
- // There are features watching for mode changes such as window manager
- // and location manager which are in our process. The callbacks in these
- // features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final ActiveCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
- active, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
- String attributionTag, @OpFlags int flags, @Mode int result,
- @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- ArraySet<StartedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mStartedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
-
- StartedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
-
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
-
- if (dispatchedCallbacks == null) {
- return;
- }
-
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpStarted,
- this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId));
- }
-
- private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
- @AttributionFlags int attributionFlags, int attributionChainId) {
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final StartedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
- result, startedType, attributionFlags, attributionChainId);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
- String attributionTag, @OpFlags int flags, @Mode int result) {
- ArraySet<NotedCallback> dispatchedCallbacks = null;
- final int callbackListCount = mNotedWatchers.size();
- for (int i = 0; i < callbackListCount; i++) {
- final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
- final NotedCallback callback = callbacks.get(code);
- if (callback != null) {
- if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
- continue;
- }
- if (dispatchedCallbacks == null) {
- dispatchedCallbacks = new ArraySet<>();
- }
- dispatchedCallbacks.add(callback);
- }
- }
- if (dispatchedCallbacks == null) {
- return;
- }
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyOpChecked,
- this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
- result));
- }
-
- private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
- int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
- @Mode int result) {
- // There are features watching for checks in our process. The callbacks in
- // these features may require permissions our remote caller does not have.
- final long identity = Binder.clearCallingIdentity();
- try {
- final int callbackCount = callbacks.size();
- for (int i = 0; i < callbackCount; i++) {
- final NotedCallback callback = callbacks.valueAt(i);
- try {
- if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
- continue;
- }
- callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
- result);
- } catch (RemoteException e) {
- /* do nothing */
- }
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
@Override
public int permissionToOpCode(String permission) {
if (permission == null) {
@@ -3451,13 +1114,6 @@
Binder.getCallingPid(), Binder.getCallingUid(), null);
}
- private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
- // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
- // as watcher should not use this to signal if the value is changed.
- return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
- watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
- }
-
private void verifyIncomingOp(int op) {
if (op >= 0 && op < AppOpsManager._NUM_OP) {
// Enforce manage appops permission if it's a restricted read op.
@@ -3498,35 +1154,6 @@
|| resolveUid(resolvedPackage) != Process.INVALID_UID;
}
- private boolean isCallerAndAttributionTrusted(@NonNull AttributionSource attributionSource) {
- if (attributionSource.getUid() != Binder.getCallingUid()
- && attributionSource.isTrusted(mContext)) {
- return true;
- }
- return mContext.checkPermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
- Binder.getCallingPid(), Binder.getCallingUid(), null)
- == PackageManager.PERMISSION_GRANTED;
- }
-
- private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null) {
- if (!edit) {
- return null;
- }
- uidState = new UidState(uid);
- mUidStates.put(uid, uidState);
- }
-
- return uidState;
- }
-
- private void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
- synchronized (this) {
- getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
- }
- }
-
/**
* @return {@link PackageManagerInternal}
*/
@@ -3538,764 +1165,6 @@
return mPackageManagerInternal;
}
- /**
- * Create a restriction description matching the properties of the package.
- *
- * @param pkg The package to create the restriction description for
- *
- * @return The restriction matching the package
- */
- private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
- return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
- mContext.checkPermission(android.Manifest.permission
- .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
- == PackageManager.PERMISSION_GRANTED);
- }
-
- /**
- * @see #verifyAndGetBypass(int, String, String, String)
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag) {
- return verifyAndGetBypass(uid, packageName, attributionTag, null);
- }
-
- /**
- * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
- * description} for the package, along with a boolean indicating whether the attribution tag is
- * valid.
- *
- * @param uid The uid the package belongs to
- * @param packageName The package the might belong to the uid
- * @param attributionTag attribution tag or {@code null} if no need to verify
- * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
- *
- * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
- * attribution tag is valid
- */
- private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
- @Nullable String attributionTag, @Nullable String proxyPackageName) {
- if (uid == Process.ROOT_UID) {
- // For backwards compatibility, don't check package name for root UID.
- return new PackageVerificationResult(null,
- /* isAttributionTagValid */ true);
- }
- if (Process.isSdkSandboxUid(uid)) {
- // SDK sandbox processes run in their own UID range, but their associated
- // UID for checks should always be the UID of the package implementing SDK sandbox
- // service.
- // TODO: We will need to modify the callers of this function instead, so
- // modifications and checks against the app ops state are done with the
- // correct UID.
- try {
- final PackageManager pm = mContext.getPackageManager();
- final String supplementalPackageName = pm.getSdkSandboxPackageName();
- if (Objects.equals(packageName, supplementalPackageName)) {
- uid = pm.getPackageUidAsUser(supplementalPackageName,
- PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
- }
- } catch (PackageManager.NameNotFoundException e) {
- // Shouldn't happen for the supplemental package
- e.printStackTrace();
- }
- }
-
-
- // Do not check if uid/packageName/attributionTag is already known.
- synchronized (this) {
- UidState uidState = mUidStates.get(uid);
- if (uidState != null && uidState.pkgOps != null) {
- Ops ops = uidState.pkgOps.get(packageName);
-
- if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
- attributionTag)) && ops.bypass != null) {
- return new PackageVerificationResult(ops.bypass,
- ops.validAttributionTags.contains(attributionTag));
- }
- }
- }
-
- int callingUid = Binder.getCallingUid();
-
- // Allow any attribution tag for resolvable uids
- int pkgUid;
- if (Objects.equals(packageName, "com.android.shell")) {
- // Special case for the shell which is a package but should be able
- // to bypass app attribution tag restrictions.
- pkgUid = Process.SHELL_UID;
- } else {
- pkgUid = resolveUid(packageName);
- }
- if (pkgUid != Process.INVALID_UID) {
- if (pkgUid != UserHandle.getAppId(uid)) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid "
- + UserHandle.getAppId(uid) + otherUidMessage);
- }
- return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
- /* isAttributionTagValid */ true);
- }
-
- int userId = UserHandle.getUserId(uid);
- RestrictionBypass bypass = null;
- boolean isAttributionTagValid = false;
-
- final long ident = Binder.clearCallingIdentity();
- try {
- PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
- AndroidPackage pkg = pmInt.getPackage(packageName);
- if (pkg != null) {
- isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
- pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
- bypass = getBypassforPackage(pkg);
- }
- if (!isAttributionTagValid) {
- AndroidPackage proxyPkg = proxyPackageName != null
- ? pmInt.getPackage(proxyPackageName) : null;
- // Re-check in proxy.
- isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
- String msg;
- if (pkg != null && isAttributionTagValid) {
- msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
- + " package " + proxyPackageName + ", this is not advised";
- } else if (pkg != null) {
- msg = "attributionTag " + attributionTag + " not declared in manifest of "
- + packageName;
- } else {
- msg = "package " + packageName + " not found, can't check for "
- + "attributionTag " + attributionTag;
- }
-
- try {
- if (!mPlatformCompat.isChangeEnabledByPackageName(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
- userId) || !mPlatformCompat.isChangeEnabledByUid(
- SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
- callingUid)) {
- // Do not override tags if overriding is not enabled for this package
- isAttributionTagValid = true;
- }
- Slog.e(TAG, msg);
- } catch (RemoteException neverHappens) {
- }
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
-
- if (pkgUid != uid) {
- Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
- + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
- String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
- throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
- + otherUidMessage);
- }
-
- return new PackageVerificationResult(bypass, isAttributionTagValid);
- }
-
- private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
- @Nullable String attributionTag) {
- if (pkg == null) {
- return false;
- } else if (attributionTag == null) {
- return true;
- }
- if (pkg.getAttributions() != null) {
- int numAttributions = pkg.getAttributions().size();
- for (int i = 0; i < numAttributions; i++) {
- if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
- return true;
- }
- }
- }
-
- return false;
- }
-
- /**
- * Get (and potentially create) ops.
- *
- * @param uid The uid the package belongs to
- * @param packageName The name of the package
- * @param attributionTag attribution tag
- * @param isAttributionTagValid whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit == false)
- * @param edit If an ops does not exist, create the ops?
-
- * @return The ops
- */
- private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
- boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
- UidState uidState = getUidStateLocked(uid, edit);
- if (uidState == null) {
- return null;
- }
-
- if (uidState.pkgOps == null) {
- if (!edit) {
- return null;
- }
- uidState.pkgOps = new ArrayMap<>();
- }
-
- Ops ops = uidState.pkgOps.get(packageName);
- if (ops == null) {
- if (!edit) {
- return null;
- }
- ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
- }
-
- if (edit) {
- if (bypass != null) {
- ops.bypass = bypass;
- }
-
- if (attributionTag != null) {
- ops.knownAttributionTags.add(attributionTag);
- if (isAttributionTagValid) {
- ops.validAttributionTags.add(attributionTag);
- } else {
- ops.validAttributionTags.remove(attributionTag);
- }
- }
- }
-
- return ops;
- }
-
- @Override
- public void scheduleWriteLocked() {
- if (!mWriteScheduled) {
- mWriteScheduled = true;
- mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
- }
- }
-
- @Override
- public void scheduleFastWriteLocked() {
- if (!mFastWriteScheduled) {
- mWriteScheduled = true;
- mFastWriteScheduled = true;
- mHandler.removeCallbacks(mWriteRunner);
- mHandler.postDelayed(mWriteRunner, 10*1000);
- }
- }
-
- /**
- * Get the state of an op for a uid.
- *
- * @param code The code of the op
- * @param uid The uid the of the package
- * @param packageName The package name for which to get the state for
- * @param attributionTag The attribution tag
- * @param isAttributionTagValid Whether the given attribution tag is valid
- * @param bypass When to bypass certain op restrictions (can be null if edit == false)
- * @param edit Iff {@code true} create the {@link Op} object if not yet created
- *
- * @return The {@link Op state} of the op
- */
- private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
- @Nullable String attributionTag, boolean isAttributionTagValid,
- @Nullable RestrictionBypass bypass, boolean edit) {
- Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
- edit);
- if (ops == null) {
- return null;
- }
- return getOpLocked(ops, code, uid, edit);
- }
-
- private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
- Op op = ops.get(code);
- if (op == null) {
- if (!edit) {
- return null;
- }
- op = new Op(ops.uidState, ops.packageName, code, uid);
- ops.put(code, op);
- }
- if (edit) {
- scheduleWriteLocked();
- }
- return op;
- }
-
- private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
- if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
- return false;
- }
- final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
- return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
- }
-
- private boolean isOpRestrictedLocked(int uid, int code, String packageName,
- String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
- int restrictionSetCount = mOpGlobalRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code)) {
- return true;
- }
- }
-
- int userHandle = UserHandle.getUserId(uid);
- restrictionSetCount = mOpUserRestrictions.size();
-
- for (int i = 0; i < restrictionSetCount; i++) {
- // For each client, check that the given op is not restricted, or that the given
- // package is exempt from the restriction.
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
- if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
- isCheckOp)) {
- RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
- if (opBypass != null) {
- // If we are the system, bypass user restrictions for certain codes
- synchronized (this) {
- if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
- return false;
- }
- if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
- return false;
- }
- if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
- && appBypass.isRecordAudioRestrictionExcept) {
- return false;
- }
- }
- }
- return true;
- }
- }
- return false;
- }
-
- void readState() {
- int oldVersion = NO_VERSION;
- synchronized (mFile) {
- synchronized (this) {
- FileInputStream stream;
- try {
- stream = mFile.openRead();
- } catch (FileNotFoundException e) {
- Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
- return;
- }
- boolean success = false;
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- try {
- TypedXmlPullParser parser = Xml.resolvePullParser(stream);
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG
- && type != XmlPullParser.END_DOCUMENT) {
- ;
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new IllegalStateException("no start tag found");
- }
-
- oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
-
- int outerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("pkg")) {
- readPackage(parser);
- } else if (tagName.equals("uid")) {
- readUidOps(parser);
- } else {
- Slog.w(TAG, "Unknown element under <app-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- success = true;
- } catch (IllegalStateException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NullPointerException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (NumberFormatException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (XmlPullParserException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IOException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } catch (IndexOutOfBoundsException e) {
- Slog.w(TAG, "Failed parsing " + e);
- } finally {
- if (!success) {
- mUidStates.clear();
- mAppOpsServiceInterface.clearAllModes();
- }
- try {
- stream.close();
- } catch (IOException e) {
- }
- }
- }
- }
- synchronized (this) {
- upgradeLocked(oldVersion);
- }
- }
-
- private void upgradeRunAnyInBackgroundLocked() {
- for (int i = 0; i < mUidStates.size(); i++) {
- final UidState uidState = mUidStates.valueAt(i);
- if (uidState == null) {
- continue;
- }
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null) {
- final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (idx >= 0) {
- uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- opModes.valueAt(idx));
- }
- }
- if (uidState.pkgOps == null) {
- continue;
- }
- boolean changed = false;
- for (int j = 0; j < uidState.pkgOps.size(); j++) {
- Ops ops = uidState.pkgOps.valueAt(j);
- if (ops != null) {
- final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
- if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
- final Op copy = new Op(op.uidState, op.packageName,
- AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
- copy.setMode(op.getMode());
- ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
- changed = true;
- }
- }
- }
- if (changed) {
- uidState.evalForegroundOps();
- }
- }
- }
-
- private void upgradeLocked(int oldVersion) {
- if (oldVersion >= CURRENT_VERSION) {
- return;
- }
- Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
- switch (oldVersion) {
- case NO_VERSION:
- upgradeRunAnyInBackgroundLocked();
- // fall through
- case 1:
- // for future upgrades
- }
- scheduleFastWriteLocked();
- }
-
- private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
- XmlPullParserException, IOException {
- final int uid = parser.getAttributeInt(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- final int code = parser.getAttributeInt(null, "n");
- final int mode = parser.getAttributeInt(null, "m");
- setUidMode(code, uid, mode);
- } else {
- Slog.w(TAG, "Unknown element under <uid-ops>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readPackage(TypedXmlPullParser parser)
- throws NumberFormatException, XmlPullParserException, IOException {
- String pkgName = parser.getAttributeValue(null, "n");
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals("uid")) {
- readUid(parser, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- }
-
- private void readUid(TypedXmlPullParser parser, String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int uid = parser.getAttributeInt(null, "n");
- final UidState uidState = getUidStateLocked(uid, true);
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("op")) {
- readOp(parser, uidState, pkgName);
- } else {
- Slog.w(TAG, "Unknown element under <pkg>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
- uidState.evalForegroundOps();
- }
-
- private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
- @Nullable String attribution)
- throws NumberFormatException, IOException, XmlPullParserException {
- final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
-
- final long key = parser.getAttributeLong(null, "n");
- final int uidState = extractUidStateFromKey(key);
- final int opFlags = extractFlagsFromKey(key);
-
- final long accessTime = parser.getAttributeLong(null, "t", 0);
- final long rejectTime = parser.getAttributeLong(null, "r", 0);
- final long accessDuration = parser.getAttributeLong(null, "d", -1);
- final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
- final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
- final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
-
- if (accessTime > 0) {
- attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
- proxyAttributionTag, uidState, opFlags);
- }
- if (rejectTime > 0) {
- attributedOp.rejected(rejectTime, uidState, opFlags);
- }
- }
-
- private void readOp(TypedXmlPullParser parser,
- @NonNull UidState uidState, @NonNull String pkgName)
- throws NumberFormatException, XmlPullParserException, IOException {
- int opCode = parser.getAttributeInt(null, "n");
- Op op = new Op(uidState, pkgName, opCode, uidState.uid);
-
- final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
- op.setMode(mode);
-
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- String tagName = parser.getName();
- if (tagName.equals("st")) {
- readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
- } else {
- Slog.w(TAG, "Unknown element under <op>: "
- + parser.getName());
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
- if (uidState.pkgOps == null) {
- uidState.pkgOps = new ArrayMap<>();
- }
- Ops ops = uidState.pkgOps.get(pkgName);
- if (ops == null) {
- ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
- }
- ops.put(op.op, op);
- }
-
- void writeState() {
- synchronized (mFile) {
- FileOutputStream stream;
- try {
- stream = mFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state: " + e);
- return;
- }
-
- List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
-
- try {
- TypedXmlSerializer out = Xml.resolveSerializer(stream);
- out.startDocument(null, true);
- out.startTag(null, "app-ops");
- out.attributeInt(null, "v", CURRENT_VERSION);
-
- SparseArray<SparseIntArray> uidStatesClone;
- synchronized (this) {
- uidStatesClone = new SparseArray<>(mUidStates.size());
-
- final int uidStateCount = mUidStates.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- UidState uidState = mUidStates.valueAt(uidStateNum);
- int uid = mUidStates.keyAt(uidStateNum);
-
- SparseIntArray opModes = uidState.getNonDefaultUidModes();
- if (opModes != null && opModes.size() > 0) {
- uidStatesClone.put(uid, opModes);
- }
- }
- }
-
- final int uidStateCount = uidStatesClone.size();
- for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
- SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
- if (opModes != null && opModes.size() > 0) {
- out.startTag(null, "uid");
- out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
- final int opCount = opModes.size();
- for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
- final int op = opModes.keyAt(opCountNum);
- final int mode = opModes.valueAt(opCountNum);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op);
- out.attributeInt(null, "m", mode);
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- }
-
- if (allOps != null) {
- String lastPkg = null;
- for (int i=0; i<allOps.size(); i++) {
- AppOpsManager.PackageOps pkg = allOps.get(i);
- if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- lastPkg = pkg.getPackageName();
- if (lastPkg != null) {
- out.startTag(null, "pkg");
- out.attribute(null, "n", lastPkg);
- }
- }
- out.startTag(null, "uid");
- out.attributeInt(null, "n", pkg.getUid());
- List<AppOpsManager.OpEntry> ops = pkg.getOps();
- for (int j=0; j<ops.size(); j++) {
- AppOpsManager.OpEntry op = ops.get(j);
- out.startTag(null, "op");
- out.attributeInt(null, "n", op.getOp());
- if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
- out.attributeInt(null, "m", op.getMode());
- }
-
- for (String attributionTag : op.getAttributedOpEntries().keySet()) {
- final AttributedOpEntry attribution =
- op.getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = attribution.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = attribution.getLastAccessTime(uidState,
- uidState, flags);
- final long rejectTime = attribution.getLastRejectTime(uidState,
- uidState, flags);
- final long accessDuration = attribution.getLastDuration(
- uidState, uidState, flags);
- // Proxy information for rejections is not backed up
- final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
- uidState, uidState, flags);
-
- if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
- && proxy == null) {
- continue;
- }
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- out.startTag(null, "st");
- if (attributionTag != null) {
- out.attribute(null, "id", attributionTag);
- }
- out.attributeLong(null, "n", key);
- if (accessTime > 0) {
- out.attributeLong(null, "t", accessTime);
- }
- if (rejectTime > 0) {
- out.attributeLong(null, "r", rejectTime);
- }
- if (accessDuration > 0) {
- out.attributeLong(null, "d", accessDuration);
- }
- if (proxyPkg != null) {
- out.attribute(null, "pp", proxyPkg);
- }
- if (proxyAttributionTag != null) {
- out.attribute(null, "pc", proxyAttributionTag);
- }
- if (proxyUid >= 0) {
- out.attributeInt(null, "pu", proxyUid);
- }
- out.endTag(null, "st");
- }
- }
-
- out.endTag(null, "op");
- }
- out.endTag(null, "uid");
- }
- if (lastPkg != null) {
- out.endTag(null, "pkg");
- }
- }
-
- out.endTag(null, "app-ops");
- out.endDocument();
- mFile.finishWrite(stream);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write state, restoring backup.", e);
- mFile.failWrite(stream);
- }
- }
- mHistoricalRegistry.writeAndClearDiscreteHistory();
- }
-
static class Shell extends ShellCommand {
final IAppOpsService mInterface;
final AppOpsService mInternal;
@@ -4309,7 +1178,6 @@
int mode;
int packageUid;
int nonpackageUid;
- final static Binder sBinder = new Binder();
IBinder mToken;
boolean targetsUid;
@@ -4330,7 +1198,7 @@
dumpCommandHelp(pw);
}
- static private int strOpToOp(String op, PrintWriter err) {
+ static int strOpToOp(String op, PrintWriter err) {
try {
return AppOpsManager.strOpToOp(op);
} catch (IllegalArgumentException e) {
@@ -4527,6 +1395,24 @@
pw.println(" not specified, the current user is assumed.");
}
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ mAppOpsService.dump(fd, pw, args);
+
+ pw.println();
+ if (mCheckOpsDelegateDispatcher.mPolicy != null
+ && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
+ AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
+ policy.dumpTags(pw);
+ } else {
+ pw.println(" AppOps policy not set.");
+ }
+
+ if (mAudioRestrictionManager.hasActiveRestrictions()) {
+ pw.println();
+ mAudioRestrictionManager.dump(pw);
+ }
+ }
static int onShellCommand(Shell shell, String cmd) {
if (cmd == null) {
return shell.handleDefaultCommands(cmd);
@@ -4730,14 +1616,12 @@
return 0;
}
case "write-settings": {
- shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
+ shell.mInternal.mAppOpsService
+ .enforceManageAppOpsModes(Binder.getCallingPid(),
Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- synchronized (shell.mInternal) {
- shell.mInternal.mHandler.removeCallbacks(shell.mInternal.mWriteRunner);
- }
- shell.mInternal.writeState();
+ shell.mInternal.mAppOpsService.writeState();
pw.println("Current settings written.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -4745,11 +1629,12 @@
return 0;
}
case "read-settings": {
- shell.mInternal.enforceManageAppOpsModes(Binder.getCallingPid(),
- Binder.getCallingUid(), -1);
+ shell.mInternal.mAppOpsService
+ .enforceManageAppOpsModes(Binder.getCallingPid(),
+ Binder.getCallingUid(), -1);
final long token = Binder.clearCallingIdentity();
try {
- shell.mInternal.readState();
+ shell.mInternal.mAppOpsService.readState();
pw.println("Last settings read.");
} finally {
Binder.restoreCallingIdentity(token);
@@ -4795,877 +1680,70 @@
return -1;
}
- private void dumpHelp(PrintWriter pw) {
- pw.println("AppOps service (appops) dump options:");
- pw.println(" -h");
- pw.println(" Print this help text.");
- pw.println(" --op [OP]");
- pw.println(" Limit output to data associated with the given app op code.");
- pw.println(" --mode [MODE]");
- pw.println(" Limit output to data associated with the given app op mode.");
- pw.println(" --package [PACKAGE]");
- pw.println(" Limit output to data associated with the given package name.");
- pw.println(" --attributionTag [attributionTag]");
- pw.println(" Limit output to data associated with the given attribution tag.");
- pw.println(" --include-discrete [n]");
- pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
- pw.println(" --watchers");
- pw.println(" Only output the watcher sections.");
- pw.println(" --history");
- pw.println(" Only output history.");
- pw.println(" --uid-state-changes");
- pw.println(" Include logs about uid state changes.");
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
- @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
- @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
- final int numAttributions = op.mAttributions.size();
- for (int i = 0; i < numAttributions; i++) {
- if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
- op.mAttributions.keyAt(i), filterAttributionTag)) {
- continue;
- }
-
- pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
- dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
- prefix + " ");
- pw.print(prefix + "]\n");
- }
- }
-
- private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
- @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
- @NonNull Date date, @NonNull String prefix) {
-
- final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
- attributionTag).getAttributedOpEntries().get(attributionTag);
-
- final ArraySet<Long> keys = entry.collectKeys();
-
- final int keyCount = keys.size();
- for (int k = 0; k < keyCount; k++) {
- final long key = keys.valueAt(k);
-
- final int uidState = AppOpsManager.extractUidStateFromKey(key);
- final int flags = AppOpsManager.extractFlagsFromKey(key);
-
- final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
- final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
- final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
- final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
-
- String proxyPkg = null;
- String proxyAttributionTag = null;
- int proxyUid = Process.INVALID_UID;
- if (proxy != null) {
- proxyPkg = proxy.getPackageName();
- proxyAttributionTag = proxy.getAttributionTag();
- proxyUid = proxy.getUid();
- }
-
- if (accessTime > 0) {
- pw.print(prefix);
- pw.print("Access: ");
- pw.print(AppOpsManager.keyToString(key));
- pw.print(" ");
- date.setTime(accessTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(accessTime - now, pw);
- pw.print(")");
- if (accessDuration > 0) {
- pw.print(" duration=");
- TimeUtils.formatDuration(accessDuration, pw);
- }
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
-
- if (rejectTime > 0) {
- pw.print(prefix);
- pw.print("Reject: ");
- pw.print(AppOpsManager.keyToString(key));
- date.setTime(rejectTime);
- pw.print(sdf.format(date));
- pw.print(" (");
- TimeUtils.formatDuration(rejectTime - now, pw);
- pw.print(")");
- if (proxyUid >= 0) {
- pw.print(" proxy[");
- pw.print("uid=");
- pw.print(proxyUid);
- pw.print(", pkg=");
- pw.print(proxyPkg);
- pw.print(", attributionTag=");
- pw.print(proxyAttributionTag);
- pw.print("]");
- }
- pw.println();
- }
- }
-
- final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
- if (attributedOp.isRunning()) {
- long earliestElapsedTime = Long.MAX_VALUE;
- long maxNumStarts = 0;
- int numInProgressEvents = attributedOp.mInProgressEvents.size();
- for (int i = 0; i < numInProgressEvents; i++) {
- AttributedOp.InProgressStartOpEvent event =
- attributedOp.mInProgressEvents.valueAt(i);
-
- earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
- maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
- }
-
- pw.print(prefix + "Running start at: ");
- TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
- pw.println();
-
- if (maxNumStarts > 1) {
- pw.print(prefix + "startNesting=");
- pw.println(maxNumStarts);
- }
- }
- }
-
- @NeverCompile // Avoid size overhead of debugging code.
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
-
- int dumpOp = OP_NONE;
- String dumpPackage = null;
- String dumpAttributionTag = null;
- int dumpUid = Process.INVALID_UID;
- int dumpMode = -1;
- boolean dumpWatchers = false;
- // TODO ntmyren: Remove the dumpHistory and dumpFilter
- boolean dumpHistory = false;
- boolean includeDiscreteOps = false;
- boolean dumpUidStateChangeLogs = false;
- int nDiscreteOps = 10;
- @HistoricalOpsRequestFilter int dumpFilter = 0;
- boolean dumpAll = false;
-
- if (args != null) {
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
- if ("-h".equals(arg)) {
- dumpHelp(pw);
- return;
- } else if ("-a".equals(arg)) {
- // dump all data
- dumpAll = true;
- } else if ("--op".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --op option");
- return;
- }
- dumpOp = Shell.strOpToOp(args[i], pw);
- dumpFilter |= FILTER_BY_OP_NAMES;
- if (dumpOp < 0) {
- return;
- }
- } else if ("--package".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --package option");
- return;
- }
- dumpPackage = args[i];
- dumpFilter |= FILTER_BY_PACKAGE_NAME;
- try {
- dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
- PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
- 0);
- } catch (RemoteException e) {
- }
- if (dumpUid < 0) {
- pw.println("Unknown package: " + dumpPackage);
- return;
- }
- dumpUid = UserHandle.getAppId(dumpUid);
- dumpFilter |= FILTER_BY_UID;
- } else if ("--attributionTag".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --attributionTag option");
- return;
- }
- dumpAttributionTag = args[i];
- dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
- } else if ("--mode".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --mode option");
- return;
- }
- dumpMode = Shell.strModeToMode(args[i], pw);
- if (dumpMode < 0) {
- return;
- }
- } else if ("--watchers".equals(arg)) {
- dumpWatchers = true;
- } else if ("--include-discrete".equals(arg)) {
- i++;
- if (i >= args.length) {
- pw.println("No argument for --include-discrete option");
- return;
- }
- try {
- nDiscreteOps = Integer.valueOf(args[i]);
- } catch (NumberFormatException e) {
- pw.println("Wrong parameter: " + args[i]);
- return;
- }
- includeDiscreteOps = true;
- } else if ("--history".equals(arg)) {
- dumpHistory = true;
- } else if (arg.length() > 0 && arg.charAt(0) == '-') {
- pw.println("Unknown option: " + arg);
- return;
- } else if ("--uid-state-changes".equals(arg)) {
- dumpUidStateChangeLogs = true;
- } else {
- pw.println("Unknown command: " + arg);
- return;
- }
- }
- }
-
- final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
- final Date date = new Date();
- synchronized (this) {
- pw.println("Current AppOps Service state:");
- if (!dumpHistory && !dumpWatchers) {
- mConstants.dump(pw);
- }
- pw.println();
- final long now = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long nowUptime = SystemClock.uptimeMillis();
- boolean needSep = false;
- if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
- && !dumpHistory) {
- pw.println(" Profile owners:");
- for (int poi = 0; poi < mProfileOwners.size(); poi++) {
- pw.print(" User #");
- pw.print(mProfileOwners.keyAt(poi));
- pw.print(": ");
- UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
- pw.println();
- }
- pw.println();
- }
-
- if (!dumpHistory) {
- needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
- }
-
- if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
- boolean printedHeader = false;
- for (int i = 0; i < mModeWatchers.size(); i++) {
- final ModeCallback cb = mModeWatchers.valueAt(i);
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
- continue;
- }
- needSep = true;
- if (!printedHeader) {
- pw.println(" All op mode watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
- pw.print(": "); pw.println(cb);
- }
- }
- if (mActiveWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
- final SparseArray<ActiveCallback> activeWatchers =
- mActiveWatchers.valueAt(watcherNum);
- if (activeWatchers.size() <= 0) {
- continue;
- }
- final ActiveCallback cb = activeWatchers.valueAt(0);
- if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op active watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mActiveWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = activeWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mStartedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
-
- final int watchersSize = mStartedWatchers.size();
- for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
- final SparseArray<StartedCallback> startedWatchers =
- mStartedWatchers.valueAt(watcherNum);
- if (startedWatchers.size() <= 0) {
- continue;
- }
-
- final StartedCallback cb = startedWatchers.valueAt(0);
- if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
-
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
-
- if (!printedHeader) {
- pw.println(" All op started watchers:");
- printedHeader = true;
- }
-
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mStartedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
-
- pw.print(" [");
- final int opCount = startedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
-
- pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
-
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mNotedWatchers.size() > 0 && dumpMode < 0) {
- needSep = true;
- boolean printedHeader = false;
- for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
- final SparseArray<NotedCallback> notedWatchers =
- mNotedWatchers.valueAt(watcherNum);
- if (notedWatchers.size() <= 0) {
- continue;
- }
- final NotedCallback cb = notedWatchers.valueAt(0);
- if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
- continue;
- }
- if (dumpPackage != null
- && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
- continue;
- }
- if (!printedHeader) {
- pw.println(" All op noted watchers:");
- printedHeader = true;
- }
- pw.print(" ");
- pw.print(Integer.toHexString(System.identityHashCode(
- mNotedWatchers.keyAt(watcherNum))));
- pw.println(" ->");
- pw.print(" [");
- final int opCount = notedWatchers.size();
- for (int opNum = 0; opNum < opCount; opNum++) {
- if (opNum > 0) {
- pw.print(' ');
- }
- pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
- if (opNum < opCount - 1) {
- pw.print(',');
- }
- }
- pw.println("]");
- pw.print(" ");
- pw.println(cb);
- }
- }
- if (mAudioRestrictionManager.hasActiveRestrictions() && dumpOp < 0
- && dumpPackage != null && dumpMode < 0 && !dumpWatchers) {
- needSep = mAudioRestrictionManager.dump(pw) || needSep;
- }
- if (needSep) {
- pw.println();
- }
- for (int i=0; i<mUidStates.size(); i++) {
- UidState uidState = mUidStates.valueAt(i);
- final SparseIntArray opModes = uidState.getNonDefaultUidModes();
- final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
-
- if (dumpWatchers || dumpHistory) {
- continue;
- }
- if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
- boolean hasOp = dumpOp < 0 || (opModes != null
- && opModes.indexOfKey(dumpOp) >= 0);
- boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
- boolean hasMode = dumpMode < 0;
- if (!hasMode && opModes != null) {
- for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
- if (opModes.valueAt(opi) == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (pkgOps != null) {
- for (int pkgi = 0;
- (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
- pkgi++) {
- Ops ops = pkgOps.valueAt(pkgi);
- if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
- hasOp = true;
- }
- if (!hasMode) {
- for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
- if (ops.valueAt(opi).getMode() == dumpMode) {
- hasMode = true;
- }
- }
- }
- if (!hasPackage && dumpPackage.equals(ops.packageName)) {
- hasPackage = true;
- }
- }
- }
- if (uidState.foregroundOps != null && !hasOp) {
- if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
- hasOp = true;
- }
- }
- if (!hasOp || !hasPackage || !hasMode) {
- continue;
- }
- }
-
- pw.print(" Uid "); UserHandle.formatUid(pw, uidState.uid); pw.println(":");
- uidState.dump(pw, nowElapsed);
- if (uidState.foregroundOps != null && (dumpMode < 0
- || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
- pw.println(" foregroundOps:");
- for (int j = 0; j < uidState.foregroundOps.size(); j++) {
- if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
- continue;
- }
- pw.print(" ");
- pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
- pw.print(": ");
- pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
- }
- pw.print(" hasForegroundWatchers=");
- pw.println(uidState.hasForegroundWatchers);
- }
- needSep = true;
-
- if (opModes != null) {
- final int opModeCount = opModes.size();
- for (int j = 0; j < opModeCount; j++) {
- final int code = opModes.keyAt(j);
- final int mode = opModes.valueAt(j);
- if (dumpOp >= 0 && dumpOp != code) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != mode) {
- continue;
- }
- pw.print(" "); pw.print(AppOpsManager.opToName(code));
- pw.print(": mode="); pw.println(AppOpsManager.modeToName(mode));
- }
- }
-
- if (pkgOps == null) {
- continue;
- }
-
- for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
- final Ops ops = pkgOps.valueAt(pkgi);
- if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
- continue;
- }
- boolean printedPackage = false;
- for (int j=0; j<ops.size(); j++) {
- final Op op = ops.valueAt(j);
- final int opCode = op.op;
- if (dumpOp >= 0 && dumpOp != opCode) {
- continue;
- }
- if (dumpMode >= 0 && dumpMode != op.getMode()) {
- continue;
- }
- if (!printedPackage) {
- pw.print(" Package "); pw.print(ops.packageName); pw.println(":");
- printedPackage = true;
- }
- pw.print(" "); pw.print(AppOpsManager.opToName(opCode));
- pw.print(" ("); pw.print(AppOpsManager.modeToName(op.getMode()));
- final int switchOp = AppOpsManager.opToSwitch(opCode);
- if (switchOp != opCode) {
- pw.print(" / switch ");
- pw.print(AppOpsManager.opToName(switchOp));
- final Op switchObj = ops.get(switchOp);
- int mode = switchObj == null
- ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
- pw.print("="); pw.print(AppOpsManager.modeToName(mode));
- }
- pw.println("): ");
- dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
- sdf, date, " ");
- }
- }
- }
- if (needSep) {
- pw.println();
- }
-
- boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
- mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
-
- if (!dumpHistory && !dumpWatchers) {
- pw.println();
- if (mCheckOpsDelegateDispatcher.mPolicy != null
- && mCheckOpsDelegateDispatcher.mPolicy instanceof AppOpsPolicy) {
- AppOpsPolicy policy = (AppOpsPolicy) mCheckOpsDelegateDispatcher.mPolicy;
- policy.dumpTags(pw);
- } else {
- pw.println(" AppOps policy not set.");
- }
- }
-
- if (dumpAll || dumpUidStateChangeLogs) {
- pw.println();
- pw.println("Uid State Changes Event Log:");
- getUidStateTracker().dumpEvents(pw);
- }
- }
-
- // Must not hold the appops lock
- if (dumpHistory && !dumpWatchers) {
- mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
- dumpFilter);
- }
- if (includeDiscreteOps) {
- pw.println("Discrete accesses: ");
- mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
- dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
- }
- }
-
@Override
public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
- checkSystemUid("setUserRestrictions");
- Objects.requireNonNull(restrictions);
- Objects.requireNonNull(token);
- for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
- String restriction = AppOpsManager.opToRestriction(i);
- if (restriction != null) {
- setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
- userHandle, null);
- }
- }
+ mAppOpsService.setUserRestrictions(restrictions, token, userHandle);
}
@Override
public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
PackageTagsList excludedPackageTags) {
- if (Binder.getCallingPid() != Process.myPid()) {
- mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
- Binder.getCallingPid(), Binder.getCallingUid(), null);
- }
- if (userHandle != UserHandle.getCallingUserId()) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
- && mContext.checkCallingOrSelfPermission(Manifest.permission
- .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
- + " INTERACT_ACROSS_USERS to interact cross user ");
- }
- }
- verifyIncomingOp(code);
- Objects.requireNonNull(token);
- setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
- }
-
- private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
- int userHandle, PackageTagsList excludedPackageTags) {
- synchronized (AppOpsService.this) {
- ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientUserRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpUserRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
- userHandle)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, this, code, UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, this, code, restricted,
- userHandle));
- }
-
- if (restrictionState.isDefault()) {
- mOpUserRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
- }
-
- private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
- synchronized (AppOpsService.this) {
- int numUids = mUidStates.size();
- for (int uidNum = 0; uidNum < numUids; uidNum++) {
- int uid = mUidStates.keyAt(uidNum);
- if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
- continue;
- }
- updateStartedOpModeForUidLocked(code, restricted, uid);
- }
- }
- }
-
- private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
-
- int numPkgOps = uidState.pkgOps.size();
- for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
- Ops ops = uidState.pkgOps.valueAt(pkgNum);
- Op op = ops != null ? ops.get(code) : null;
- if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
- continue;
- }
- int numAttrTags = op.mAttributions.size();
- for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
- AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
- if (restricted && attrOp.isRunning()) {
- attrOp.pause();
- } else if (attrOp.isPaused()) {
- attrOp.resume();
- }
- }
- }
- }
-
- private void notifyWatchersOfChange(int code, int uid) {
- final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
- synchronized (this) {
- modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
- if (modeChangedListenerSet == null) {
- return;
- }
- }
-
- notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ mAppOpsService.setUserRestriction(code, restricted, token, userHandle,
+ excludedPackageTags);
}
@Override
public void removeUser(int userHandle) throws RemoteException {
- checkSystemUid("removeUser");
- synchronized (AppOpsService.this) {
- final int tokenCount = mOpUserRestrictions.size();
- for (int i = tokenCount - 1; i >= 0; i--) {
- ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
- opRestrictions.removeUser(userHandle);
- }
- removeUidsForUserLocked(userHandle);
- }
+ mAppOpsService.removeUser(userHandle);
}
@Override
public boolean isOperationActive(int code, int uid, String packageName) {
- if (Binder.getCallingUid() != uid) {
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
- != PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- }
- verifyIncomingOp(code);
- if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
- return false;
- }
-
- final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
- if (resolvedPackageName == null) {
- return false;
- }
- // TODO moltmann: Allow to check for attribution op activeness
- synchronized (AppOpsService.this) {
- Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
- if (pkgOps == null) {
- return false;
- }
-
- Op op = pkgOps.get(code);
- if (op == null) {
- return false;
- }
-
- return op.isRunning();
- }
+ return mAppOpsService.isOperationActive(code, uid, packageName);
}
@Override
public boolean isProxying(int op, @NonNull String proxyPackageName,
@NonNull String proxyAttributionTag, int proxiedUid,
@NonNull String proxiedPackageName) {
- Objects.requireNonNull(proxyPackageName);
- Objects.requireNonNull(proxiedPackageName);
- final long callingUid = Binder.getCallingUid();
- final long identity = Binder.clearCallingIdentity();
- try {
- final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
- proxiedPackageName, new int[] {op});
- if (packageOps == null || packageOps.isEmpty()) {
- return false;
- }
- final List<OpEntry> opEntries = packageOps.get(0).getOps();
- if (opEntries.isEmpty()) {
- return false;
- }
- final OpEntry opEntry = opEntries.get(0);
- if (!opEntry.isRunning()) {
- return false;
- }
- final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
- OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
- return proxyInfo != null && callingUid == proxyInfo.getUid()
- && proxyPackageName.equals(proxyInfo.getPackageName())
- && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ return mAppOpsService.isProxying(op, proxyPackageName, proxyAttributionTag,
+ proxiedUid, proxiedPackageName);
}
@Override
public void resetPackageOpsNoHistory(@NonNull String packageName) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetPackageOpsNoHistory");
- synchronized (AppOpsService.this) {
- final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
- UserHandle.getCallingUserId());
- if (uid == Process.INVALID_UID) {
- return;
- }
- UidState uidState = mUidStates.get(uid);
- if (uidState == null || uidState.pkgOps == null) {
- return;
- }
- Ops removedOps = uidState.pkgOps.remove(packageName);
- mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
- if (removedOps != null) {
- scheduleFastWriteLocked();
- }
- }
+ mAppOpsService.resetPackageOpsNoHistory(packageName);
}
@Override
public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
long baseSnapshotInterval, int compressionStep) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "setHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ mAppOpsService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
}
@Override
public void offsetHistory(long offsetMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "offsetHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.offsetHistory(offsetMillis);
- mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+ mAppOpsService.offsetHistory(offsetMillis);
}
@Override
public void addHistoricalOps(HistoricalOps ops) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "addHistoricalOps");
- // Must not hold the appops lock
- mHistoricalRegistry.addHistoricalOps(ops);
+ mAppOpsService.addHistoricalOps(ops);
}
@Override
public void resetHistoryParameters() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "resetHistoryParameters");
- // Must not hold the appops lock
- mHistoricalRegistry.resetHistoryParameters();
+ mAppOpsService.resetHistoryParameters();
}
@Override
public void clearHistory() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "clearHistory");
- // Must not hold the appops lock
- mHistoricalRegistry.clearAllHistory();
+ mAppOpsService.clearHistory();
}
@Override
public void rebootHistory(long offlineDurationMillis) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
- "rebootHistory");
-
- Preconditions.checkArgument(offlineDurationMillis >= 0);
-
- // Must not hold the appops lock
- mHistoricalRegistry.shutdown();
-
- if (offlineDurationMillis > 0) {
- SystemClock.sleep(offlineDurationMillis);
- }
-
- mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
- mHistoricalRegistry.systemReady(mContext.getContentResolver());
- mHistoricalRegistry.persistPendingHistory();
+ mAppOpsService.rebootHistory(offlineDurationMillis);
}
/**
@@ -5920,24 +1998,6 @@
return false;
}
- @GuardedBy("this")
- private void removeUidsForUserLocked(int userHandle) {
- for (int i = mUidStates.size() - 1; i >= 0; --i) {
- final int uid = mUidStates.keyAt(i);
- if (UserHandle.getUserId(uid) == userHandle) {
- mUidStates.valueAt(i).clear();
- mUidStates.removeAt(i);
- }
- }
- }
-
- private void checkSystemUid(String function) {
- int uid = Binder.getCallingUid();
- if (uid != Process.SYSTEM_UID) {
- throw new SecurityException(function + " must by called by the system");
- }
- }
-
private static int resolveUid(String packageName) {
if (packageName == null) {
return Process.INVALID_UID;
@@ -5958,184 +2018,43 @@
return Process.INVALID_UID;
}
- private static String[] getPackagesForUid(int uid) {
- String[] packageNames = null;
-
- // Very early during boot the package manager is not yet or not yet fully started. At this
- // time there are no packages yet.
- if (AppGlobals.getPackageManager() != null) {
- try {
- packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
- } catch (RemoteException e) {
- /* ignore - local call */
- }
- }
- if (packageNames == null) {
- return EmptyArray.STRING;
- }
- return packageNames;
- }
-
- private final class ClientUserRestrictionState implements DeathRecipient {
- private final IBinder token;
-
- ClientUserRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.token = token;
- }
-
- public boolean setRestriction(int code, boolean restricted,
- PackageTagsList excludedPackageTags, int userId) {
- return mAppOpsRestrictions.setUserRestriction(token, userId, code,
- restricted, excludedPackageTags);
- }
-
- public boolean hasRestriction(int code, String packageName, String attributionTag,
- int userId, boolean isCheckOp) {
- return mAppOpsRestrictions.getUserRestriction(token, userId, code, packageName,
- attributionTag, isCheckOp);
- }
-
- public void removeUser(int userId) {
- mAppOpsRestrictions.clearUserRestrictions(token, userId);
- }
-
- public boolean isDefault() {
- return !mAppOpsRestrictions.hasUserRestrictions(token);
- }
-
- @Override
- public void binderDied() {
- synchronized (AppOpsService.this) {
- mAppOpsRestrictions.clearUserRestrictions(token);
- mOpUserRestrictions.remove(token);
- destroy();
- }
- }
-
- public void destroy() {
- token.unlinkToDeath(this, 0);
- }
- }
-
- private final class ClientGlobalRestrictionState implements DeathRecipient {
- final IBinder mToken;
-
- ClientGlobalRestrictionState(IBinder token)
- throws RemoteException {
- token.linkToDeath(this, 0);
- this.mToken = token;
- }
-
- boolean setRestriction(int code, boolean restricted) {
- return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
- }
-
- boolean hasRestriction(int code) {
- return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
- }
-
- boolean isDefault() {
- return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
- }
-
- @Override
- public void binderDied() {
- mAppOpsRestrictions.clearGlobalRestrictions(mToken);
- mOpGlobalRestrictions.remove(mToken);
- destroy();
- }
-
- void destroy() {
- mToken.unlinkToDeath(this, 0);
- }
- }
-
private final class AppOpsManagerInternalImpl extends AppOpsManagerInternal {
@Override public void setDeviceAndProfileOwners(SparseIntArray owners) {
- synchronized (AppOpsService.this) {
- mProfileOwners = owners;
- }
+ AppOpsService.this.mAppOpsService.setDeviceAndProfileOwners(owners);
}
@Override
public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames,
boolean visible) {
- AppOpsService.this.updateAppWidgetVisibility(uidPackageNames, visible);
+ AppOpsService.this.mAppOpsService
+ .updateAppWidgetVisibility(uidPackageNames, visible);
}
@Override
public void setUidModeFromPermissionPolicy(int code, int uid, int mode,
@Nullable IAppOpsCallback callback) {
- setUidMode(code, uid, mode, callback);
+ AppOpsService.this.mAppOpsService.setUidMode(code, uid, mode, callback);
}
@Override
public void setModeFromPermissionPolicy(int code, int uid, @NonNull String packageName,
int mode, @Nullable IAppOpsCallback callback) {
- setMode(code, uid, packageName, mode, callback);
+ AppOpsService.this.mAppOpsService
+ .setMode(code, uid, packageName, mode, callback);
}
@Override
public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
- if (Binder.getCallingPid() != Process.myPid()) {
- // TODO instead of this enforcement put in AppOpsManagerInternal
- throw new SecurityException("Only the system can set global restrictions");
- }
-
- synchronized (AppOpsService.this) {
- ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
-
- if (restrictionState == null) {
- try {
- restrictionState = new ClientGlobalRestrictionState(token);
- } catch (RemoteException e) {
- return;
- }
- mOpGlobalRestrictions.put(token, restrictionState);
- }
-
- if (restrictionState.setRestriction(code, restricted)) {
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::notifyWatchersOfChange, AppOpsService.this, code,
- UID_ANY));
- mHandler.sendMessage(PooledLambda.obtainMessage(
- AppOpsService::updateStartedOpModeForUser, AppOpsService.this,
- code, restricted, UserHandle.USER_ALL));
- }
-
- if (restrictionState.isDefault()) {
- mOpGlobalRestrictions.remove(token);
- restrictionState.destroy();
- }
- }
+ AppOpsService.this.mAppOpsService
+ .setGlobalRestriction(code, restricted, token);
}
@Override
public int getOpRestrictionCount(int code, UserHandle user, String pkg,
String attributionTag) {
- int number = 0;
- synchronized (AppOpsService.this) {
- int numRestrictions = mOpUserRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpUserRestrictions.valueAt(i)
- .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
- false)) {
- number++;
- }
- }
-
- numRestrictions = mOpGlobalRestrictions.size();
- for (int i = 0; i < numRestrictions; i++) {
- if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
- number++;
- }
- }
- }
-
- return number;
+ return AppOpsService.this.mAppOpsService
+ .getOpRestrictionCount(code, user, pkg, attributionTag);
}
}
@@ -6431,7 +2350,7 @@
attributionFlags, attributionChainId, AppOpsService.this::startOperationImpl);
}
- public SyncNotedAppOp startProxyOperation(@NonNull IBinder clientId, int code,
+ public SyncNotedAppOp startProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6461,7 +2380,7 @@
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
}
- private SyncNotedAppOp startDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private SyncNotedAppOp startDelegateProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean startIfModeDefault,
boolean shouldCollectAsyncNotedOp, String message, boolean shouldCollectMessage,
boolean skipProxyOperation, @AttributionFlags int proxyAttributionFlags,
@@ -6495,7 +2414,7 @@
AppOpsService.this::finishOperationImpl);
}
- public void finishProxyOperation(@NonNull IBinder clientId, int code,
+ public void finishProxyOperation(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
if (mPolicy != null) {
if (mCheckOpsDelegate != null) {
@@ -6513,7 +2432,7 @@
}
}
- private Void finishDelegateProxyOperationImpl(@NonNull IBinder clientId, int code,
+ private Void finishDelegateProxyOperationImpl(IBinder clientId, int code,
@NonNull AttributionSource attributionSource, boolean skipProxyOperation) {
mCheckOpsDelegate.finishProxyOperation(clientId, code, attributionSource,
skipProxyOperation, AppOpsService.this::finishProxyOperationImpl);
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
new file mode 100644
index 0000000..70f3bcc
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -0,0 +1,4679 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.AttributedOpEntry;
+import static android.app.AppOpsManager.AttributionFlags;
+import static android.app.AppOpsManager.CALL_BACK_ON_SWITCHED_OP;
+import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS;
+import static android.app.AppOpsManager.HistoricalOps;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
+import static android.app.AppOpsManager.KEY_BG_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_FG_SERVICE_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.KEY_TOP_STATE_SETTLE_TIME;
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_DEFAULT;
+import static android.app.AppOpsManager.MODE_ERRORED;
+import static android.app.AppOpsManager.MODE_FOREGROUND;
+import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.Mode;
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.app.AppOpsManager.OP_PLAY_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_VIBRATE;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
+import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
+import static android.app.AppOpsManager.OpEntry;
+import static android.app.AppOpsManager.OpEventProxyInfo;
+import static android.app.AppOpsManager.OpFlags;
+import static android.app.AppOpsManager.RestrictionBypass;
+import static android.app.AppOpsManager.SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE;
+import static android.app.AppOpsManager._NUM_OP;
+import static android.app.AppOpsManager.extractFlagsFromKey;
+import static android.app.AppOpsManager.extractUidStateFromKey;
+import static android.app.AppOpsManager.modeToName;
+import static android.app.AppOpsManager.opAllowSystemBypassRestriction;
+import static android.app.AppOpsManager.opRestrictsRead;
+import static android.app.AppOpsManager.opToName;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
+
+import static com.android.server.appop.AppOpsServiceImpl.ModeCallback.ALL_OPS;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.PackageTagsList;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.storage.StorageManagerInternal;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.os.Clock;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.LocalServices;
+import com.android.server.LockGuard;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedAttribution;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import libcore.util.EmptyArray;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+class AppOpsServiceImpl implements AppOpsServiceInterface {
+ static final String TAG = "AppOps";
+ static final boolean DEBUG = false;
+
+ private static final int NO_VERSION = -1;
+ /**
+ * Increment by one every time and add the corresponding upgrade logic in
+ * {@link #upgradeLocked(int)} below. The first version was 1
+ */
+ private static final int CURRENT_VERSION = 1;
+
+ // Write at most every 30 minutes.
+ static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
+
+ // Constant meaning that any UID should be matched when dispatching callbacks
+ private static final int UID_ANY = -2;
+
+ private static final int[] OPS_RESTRICTED_ON_SUSPEND = {
+ OP_PLAY_AUDIO,
+ OP_RECORD_AUDIO,
+ OP_CAMERA,
+ OP_VIBRATE,
+ };
+ private static final int MAX_UNUSED_POOLED_OBJECTS = 3;
+
+ final Context mContext;
+ final AtomicFile mFile;
+ final Handler mHandler;
+
+ /**
+ * Pool for {@link AttributedOp.OpEventProxyInfoPool} to avoid to constantly reallocate new
+ * objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.OpEventProxyInfoPool mOpEventProxyInfoPool =
+ new AttributedOp.OpEventProxyInfoPool(MAX_UNUSED_POOLED_OBJECTS);
+
+ /**
+ * Pool for {@link AttributedOp.InProgressStartOpEventPool} to avoid to constantly reallocate
+ * new objects
+ */
+ @GuardedBy("this")
+ final AttributedOp.InProgressStartOpEventPool mInProgressStartOpEventPool =
+ new AttributedOp.InProgressStartOpEventPool(mOpEventProxyInfoPool,
+ MAX_UNUSED_POOLED_OBJECTS);
+ @Nullable
+ private final DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+ boolean mWriteScheduled;
+ boolean mFastWriteScheduled;
+ final Runnable mWriteRunner = new Runnable() {
+ public void run() {
+ synchronized (AppOpsServiceImpl.this) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ writeState();
+ return null;
+ }
+ };
+ task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+ }
+ };
+
+ @GuardedBy("this")
+ @VisibleForTesting
+ final SparseArray<UidState> mUidStates = new SparseArray<>();
+
+ volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+
+ /*
+ * These are app op restrictions imposed per user from various parties.
+ */
+ private final ArrayMap<IBinder, ClientUserRestrictionState> mOpUserRestrictions =
+ new ArrayMap<>();
+
+ /*
+ * These are app op restrictions imposed globally from various parties within the system.
+ */
+ private final ArrayMap<IBinder, ClientGlobalRestrictionState> mOpGlobalRestrictions =
+ new ArrayMap<>();
+
+ SparseIntArray mProfileOwners;
+
+ /**
+ * Reverse lookup for {@link AppOpsManager#opToSwitch(int)}. Initialized once and never
+ * changed
+ */
+ private final SparseArray<int[]> mSwitchedOps = new SparseArray<>();
+
+ /**
+ * Package Manager internal. Access via {@link #getPackageManagerInternal()}
+ */
+ private @Nullable PackageManagerInternal mPackageManagerInternal;
+
+ /**
+ * Interface for app-op modes.
+ */
+ @VisibleForTesting
+ AppOpsCheckingServiceInterface mAppOpsServiceInterface;
+
+ /**
+ * Interface for app-op restrictions.
+ */
+ @VisibleForTesting
+ AppOpsRestrictions mAppOpsRestrictions;
+
+ private AppOpsUidStateTracker mUidStateTracker;
+
+ /**
+ * Hands the definition of foreground and uid states
+ */
+ @GuardedBy("this")
+ public AppOpsUidStateTracker getUidStateTracker() {
+ if (mUidStateTracker == null) {
+ mUidStateTracker = new AppOpsUidStateTrackerImpl(
+ LocalServices.getService(ActivityManagerInternal.class),
+ mHandler,
+ r -> {
+ synchronized (AppOpsServiceImpl.this) {
+ r.run();
+ }
+ },
+ Clock.SYSTEM_CLOCK, mConstants);
+
+ mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+ this::onUidStateChanged);
+ }
+ return mUidStateTracker;
+ }
+
+ /**
+ * All times are in milliseconds. These constants are kept synchronized with the system
+ * global Settings. Any access to this class or its fields should be done while
+ * holding the AppOpsService lock.
+ */
+ final class Constants extends ContentObserver {
+
+ /**
+ * How long we want for a drop in uid state from top to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_TOP_STATE_SETTLE_TIME
+ */
+ public long TOP_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from foreground to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_FG_SERVICE_STATE_SETTLE_TIME
+ */
+ public long FG_SERVICE_STATE_SETTLE_TIME;
+
+ /**
+ * How long we want for a drop in uid state from background to settle before applying it.
+ *
+ * @see Settings.Global#APP_OPS_CONSTANTS
+ * @see AppOpsManager#KEY_BG_STATE_SETTLE_TIME
+ */
+ public long BG_STATE_SETTLE_TIME;
+
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+ private ContentResolver mResolver;
+
+ Constants(Handler handler) {
+ super(handler);
+ updateConstants();
+ }
+
+ public void startMonitoring(ContentResolver resolver) {
+ mResolver = resolver;
+ mResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.APP_OPS_CONSTANTS),
+ false, this);
+ updateConstants();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateConstants();
+ }
+
+ private void updateConstants() {
+ String value = mResolver != null ? Settings.Global.getString(mResolver,
+ Settings.Global.APP_OPS_CONSTANTS) : "";
+
+ synchronized (AppOpsServiceImpl.this) {
+ try {
+ mParser.setString(value);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad app ops settings", e);
+ }
+ TOP_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_TOP_STATE_SETTLE_TIME, 5 * 1000L);
+ FG_SERVICE_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_FG_SERVICE_STATE_SETTLE_TIME, 5 * 1000L);
+ BG_STATE_SETTLE_TIME = mParser.getDurationMillis(
+ KEY_BG_STATE_SETTLE_TIME, 1 * 1000L);
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.println(" Settings:");
+
+ pw.print(" ");
+ pw.print(KEY_TOP_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(TOP_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" ");
+ pw.print(KEY_FG_SERVICE_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(FG_SERVICE_STATE_SETTLE_TIME, pw);
+ pw.println();
+ pw.print(" ");
+ pw.print(KEY_BG_STATE_SETTLE_TIME);
+ pw.print("=");
+ TimeUtils.formatDuration(BG_STATE_SETTLE_TIME, pw);
+ pw.println();
+ }
+ }
+
+ @VisibleForTesting
+ final Constants mConstants;
+
+ @VisibleForTesting
+ final class UidState {
+ public final int uid;
+
+ public ArrayMap<String, Ops> pkgOps;
+
+ // true indicates there is an interested observer, false there isn't but it has such an op
+ //TODO: Move foregroundOps and hasForegroundWatchers into the AppOpsServiceInterface.
+ public SparseBooleanArray foregroundOps;
+ public boolean hasForegroundWatchers;
+
+ public UidState(int uid) {
+ this.uid = uid;
+ }
+
+ public void clear() {
+ mAppOpsServiceInterface.removeUid(uid);
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+ }
+ pkgOps = null;
+ }
+
+ public boolean isDefault() {
+ boolean areAllPackageModesDefault = true;
+ if (pkgOps != null) {
+ for (String packageName : pkgOps.keySet()) {
+ if (!mAppOpsServiceInterface.arePackageModesDefault(packageName,
+ UserHandle.getUserId(uid))) {
+ areAllPackageModesDefault = false;
+ break;
+ }
+ }
+ }
+ return (pkgOps == null || pkgOps.isEmpty())
+ && mAppOpsServiceInterface.areUidModesDefault(uid)
+ && areAllPackageModesDefault;
+ }
+
+ // Functions for uid mode access and manipulation.
+ public SparseIntArray getNonDefaultUidModes() {
+ return mAppOpsServiceInterface.getNonDefaultUidModes(uid);
+ }
+
+ public int getUidMode(int op) {
+ return mAppOpsServiceInterface.getUidMode(uid, op);
+ }
+
+ public boolean setUidMode(int op, int mode) {
+ return mAppOpsServiceInterface.setUidMode(uid, op, mode);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ int evalMode(int op, int mode) {
+ return getUidStateTracker().evalMode(uid, op, mode);
+ }
+
+ public void evalForegroundOps() {
+ foregroundOps = null;
+ foregroundOps = mAppOpsServiceInterface.evalForegroundUidOps(uid, foregroundOps);
+ if (pkgOps != null) {
+ for (int i = pkgOps.size() - 1; i >= 0; i--) {
+ foregroundOps = mAppOpsServiceInterface
+ .evalForegroundPackageOps(pkgOps.valueAt(i).packageName,
+ foregroundOps,
+ UserHandle.getUserId(uid));
+ }
+ }
+ hasForegroundWatchers = false;
+ if (foregroundOps != null) {
+ for (int i = 0; i < foregroundOps.size(); i++) {
+ if (foregroundOps.valueAt(i)) {
+ hasForegroundWatchers = true;
+ break;
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public int getState() {
+ return getUidStateTracker().getUidState(uid);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ public void dump(PrintWriter pw, long nowElapsed) {
+ getUidStateTracker().dumpUidState(pw, uid, nowElapsed);
+ }
+ }
+
+ static final class Ops extends SparseArray<Op> {
+ final String packageName;
+ final UidState uidState;
+
+ /**
+ * The restriction properties of the package. If {@code null} it could not have been read
+ * yet and has to be refreshed.
+ */
+ @Nullable RestrictionBypass bypass;
+
+ /** Lazily populated cache of attributionTags of this package */
+ final @NonNull ArraySet<String> knownAttributionTags = new ArraySet<>();
+
+ /**
+ * Lazily populated cache of <b>valid</b> attributionTags of this package, a set smaller
+ * than or equal to {@link #knownAttributionTags}.
+ */
+ final @NonNull ArraySet<String> validAttributionTags = new ArraySet<>();
+
+ Ops(String _packageName, UidState _uidState) {
+ packageName = _packageName;
+ uidState = _uidState;
+ }
+ }
+
+ /** Returned from {@link #verifyAndGetBypass(int, String, String, String)}. */
+ private static final class PackageVerificationResult {
+
+ final RestrictionBypass bypass;
+ final boolean isAttributionTagValid;
+
+ PackageVerificationResult(RestrictionBypass bypass, boolean isAttributionTagValid) {
+ this.bypass = bypass;
+ this.isAttributionTagValid = isAttributionTagValid;
+ }
+ }
+
+ final class Op {
+ int op;
+ int uid;
+ final UidState uidState;
+ final @NonNull String packageName;
+
+ /** attributionTag -> AttributedOp */
+ final ArrayMap<String, AttributedOp> mAttributions = new ArrayMap<>(1);
+
+ Op(UidState uidState, String packageName, int op, int uid) {
+ this.op = op;
+ this.uid = uid;
+ this.uidState = uidState;
+ this.packageName = packageName;
+ }
+
+ @Mode int getMode() {
+ return mAppOpsServiceInterface.getPackageMode(packageName, this.op,
+ UserHandle.getUserId(this.uid));
+ }
+
+ void setMode(@Mode int mode) {
+ mAppOpsServiceInterface.setPackageMode(packageName, this.op, mode,
+ UserHandle.getUserId(this.uid));
+ }
+
+ void removeAttributionsWithNoTime() {
+ for (int i = mAttributions.size() - 1; i >= 0; i--) {
+ if (!mAttributions.valueAt(i).hasAnyTime()) {
+ mAttributions.removeAt(i);
+ }
+ }
+ }
+
+ private @NonNull AttributedOp getOrCreateAttribution(@NonNull Op parent,
+ @Nullable String attributionTag) {
+ AttributedOp attributedOp;
+
+ attributedOp = mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ attributedOp = new AttributedOp(AppOpsServiceImpl.this, attributionTag,
+ parent);
+ mAttributions.put(attributionTag, attributedOp);
+ }
+
+ return attributedOp;
+ }
+
+ @NonNull
+ OpEntry createEntryLocked() {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AppOpsManager.AttributedOpEntry> attributionEntries =
+ new ArrayMap<>(numAttributions);
+ for (int i = 0; i < numAttributions; i++) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ @NonNull
+ OpEntry createSingleAttributionEntryLocked(@Nullable String attributionTag) {
+ final int numAttributions = mAttributions.size();
+
+ final ArrayMap<String, AttributedOpEntry> attributionEntries = new ArrayMap<>(1);
+ for (int i = 0; i < numAttributions; i++) {
+ if (Objects.equals(mAttributions.keyAt(i), attributionTag)) {
+ attributionEntries.put(mAttributions.keyAt(i),
+ mAttributions.valueAt(i).createAttributedOpEntryLocked());
+ break;
+ }
+ }
+
+ return new OpEntry(op, getMode(), attributionEntries);
+ }
+
+ boolean isRunning() {
+ final int numAttributions = mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (mAttributions.valueAt(i).isRunning()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ final ArrayMap<IBinder, ModeCallback> mModeWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<ActiveCallback>> mActiveWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<StartedCallback>> mStartedWatchers = new ArrayMap<>();
+ final ArrayMap<IBinder, SparseArray<NotedCallback>> mNotedWatchers = new ArrayMap<>();
+
+ final class ModeCallback extends OnOpModeChangedListener implements DeathRecipient {
+ /** If mWatchedOpCode==ALL_OPS notify for ops affected by the switch-op */
+ public static final int ALL_OPS = -2;
+
+ // Need to keep this only because stopWatchingMode needs an IAppOpsCallback.
+ // Otherwise we can just use the IBinder object.
+ private final IAppOpsCallback mCallback;
+
+ ModeCallback(IAppOpsCallback callback, int watchingUid, int flags, int watchedOpCode,
+ int callingUid, int callingPid) {
+ super(watchingUid, flags, watchedOpCode, callingUid, callingPid);
+ this.mCallback = callback;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ModeCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, getWatchingUid());
+ sb.append(" flags=0x");
+ sb.append(Integer.toHexString(getFlags()));
+ switch (getWatchedOpCode()) {
+ case OP_NONE:
+ break;
+ case ALL_OPS:
+ sb.append(" op=(all)");
+ break;
+ default:
+ sb.append(" op=");
+ sb.append(opToName(getWatchedOpCode()));
+ break;
+ }
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, getCallingUid());
+ sb.append(" pid=");
+ sb.append(getCallingPid());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void unlinkToDeath() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingMode(mCallback);
+ }
+
+ @Override
+ public void onOpModeChanged(int op, int uid, String packageName) throws RemoteException {
+ mCallback.opChanged(op, uid, packageName);
+ }
+ }
+
+ final class ActiveCallback implements DeathRecipient {
+ final IAppOpsActiveCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ ActiveCallback(IAppOpsActiveCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("ActiveCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingActive(mCallback);
+ }
+ }
+
+ final class StartedCallback implements DeathRecipient {
+ final IAppOpsStartedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ StartedCallback(IAppOpsStartedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("StartedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingStarted(mCallback);
+ }
+ }
+
+ final class NotedCallback implements DeathRecipient {
+ final IAppOpsNotedCallback mCallback;
+ final int mWatchingUid;
+ final int mCallingUid;
+ final int mCallingPid;
+
+ NotedCallback(IAppOpsNotedCallback callback, int watchingUid, int callingUid,
+ int callingPid) {
+ mCallback = callback;
+ mWatchingUid = watchingUid;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ try {
+ mCallback.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ /*ignored*/
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("NotedCallback{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" watchinguid=");
+ UserHandle.formatUid(sb, mWatchingUid);
+ sb.append(" from uid=");
+ UserHandle.formatUid(sb, mCallingUid);
+ sb.append(" pid=");
+ sb.append(mCallingPid);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ void destroy() {
+ mCallback.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ stopWatchingNoted(mCallback);
+ }
+ }
+
+ /**
+ * Call {@link AttributedOp#onClientDeath attributedOp.onClientDeath(clientId)}.
+ */
+ static void onClientDeath(@NonNull AttributedOp attributedOp,
+ @NonNull IBinder clientId) {
+ attributedOp.onClientDeath(clientId);
+ }
+
+ AppOpsServiceImpl(File storagePath, Handler handler, Context context) {
+ mContext = context;
+
+ for (int switchedCode = 0; switchedCode < _NUM_OP; switchedCode++) {
+ int switchCode = AppOpsManager.opToSwitch(switchedCode);
+ mSwitchedOps.put(switchCode,
+ ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
+ }
+ mAppOpsServiceInterface =
+ new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+ mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
+ mAppOpsServiceInterface);
+
+ LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
+ mFile = new AtomicFile(storagePath, "appops");
+
+ mHandler = handler;
+ mConstants = new Constants(mHandler);
+ readState();
+ }
+
+ /**
+ * Handler for work when packages are removed or updated
+ */
+ private BroadcastReceiver mOnPackageUpdatedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+
+ if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ mAppOpsServiceInterface.removePackage(pkgName, UserHandle.getUserId(uid));
+ Ops removedOps = uidState.pkgOps.remove(pkgName);
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
+ if (pkg == null) {
+ return;
+ }
+
+ ArrayMap<String, String> dstAttributionTags = new ArrayMap<>();
+ ArraySet<String> attributionTags = new ArraySet<>();
+ attributionTags.add(null);
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ ParsedAttribution attribution = pkg.getAttributions().get(attributionNum);
+ attributionTags.add(attribution.getTag());
+
+ int numInheritFrom = attribution.getInheritFrom().size();
+ for (int inheritFromNum = 0; inheritFromNum < numInheritFrom;
+ inheritFromNum++) {
+ dstAttributionTags.put(attribution.getInheritFrom().get(inheritFromNum),
+ attribution.getTag());
+ }
+ }
+ }
+
+ synchronized (AppOpsServiceImpl.this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ return;
+ }
+
+ // Reset cached package properties to re-initialize when needed
+ ops.bypass = null;
+ ops.knownAttributionTags.clear();
+
+ // Merge data collected for removed attributions into their successor
+ // attributions
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = numAttributions - 1; attributionNum >= 0;
+ attributionNum--) {
+ String attributionTag = op.mAttributions.keyAt(attributionNum);
+
+ if (attributionTags.contains(attributionTag)) {
+ // attribution still exist after upgrade
+ continue;
+ }
+
+ String newAttributionTag = dstAttributionTags.get(attributionTag);
+
+ AttributedOp newAttributedOp = op.getOrCreateAttribution(op,
+ newAttributionTag);
+ newAttributedOp.add(op.mAttributions.valueAt(attributionNum));
+ op.mAttributions.removeAt(attributionNum);
+
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ public void systemReady() {
+ mConstants.startMonitoring(mContext.getContentResolver());
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+
+ IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addDataScheme("package");
+
+ mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+ packageUpdateFilter, null, null);
+
+ synchronized (this) {
+ for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
+ int uid = mUidStates.keyAt(uidNum);
+ UidState uidState = mUidStates.valueAt(uidNum);
+
+ String[] pkgsInUid = getPackagesForUid(uidState.uid);
+ if (ArrayUtils.isEmpty(pkgsInUid)) {
+ uidState.clear();
+ mUidStates.removeAt(uidNum);
+ scheduleFastWriteLocked();
+ continue;
+ }
+
+ ArrayMap<String, Ops> pkgs = uidState.pkgOps;
+ if (pkgs == null) {
+ continue;
+ }
+
+ int numPkgs = pkgs.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ String pkg = pkgs.keyAt(pkgNum);
+
+ String action;
+ if (!ArrayUtils.contains(pkgsInUid, pkg)) {
+ action = Intent.ACTION_PACKAGE_REMOVED;
+ } else {
+ action = Intent.ACTION_PACKAGE_REPLACED;
+ }
+
+ SystemServerInitThreadPool.submit(
+ () -> mOnPackageUpdatedReceiver.onReceive(mContext, new Intent(action)
+ .setData(Uri.fromParts("package", pkg, null))
+ .putExtra(Intent.EXTRA_UID, uid)),
+ "Update app-ops uidState in case package " + pkg + " changed");
+ }
+ }
+ }
+
+ final IntentFilter packageSuspendFilter = new IntentFilter();
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
+ packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
+ final String[] changedPkgs = intent.getStringArrayExtra(
+ Intent.EXTRA_CHANGED_PACKAGE_LIST);
+ for (int code : OPS_RESTRICTED_ON_SUSPEND) {
+ ArraySet<OnOpModeChangedListener> onModeChangedListeners;
+ synchronized (AppOpsServiceImpl.this) {
+ onModeChangedListeners =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (onModeChangedListeners == null) {
+ continue;
+ }
+ }
+ for (int i = 0; i < changedUids.length; i++) {
+ final int changedUid = changedUids[i];
+ final String changedPkg = changedPkgs[i];
+ // We trust packagemanager to insert matching uid and packageNames in the
+ // extras
+ notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg);
+ }
+ }
+ }
+ }, UserHandle.ALL, packageSuspendFilter, null, null);
+ }
+
+ @Override
+ public void packageRemoved(int uid, String packageName) {
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ return;
+ }
+
+ Ops removedOps = null;
+
+ // Remove any package state if such.
+ if (uidState.pkgOps != null) {
+ removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ }
+
+ // If we just nuked the last package state check if the UID is valid.
+ if (removedOps != null && uidState.pkgOps.isEmpty()
+ && getPackagesForUid(uid).length <= 0) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+
+ final int numOps = removedOps.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ final Op op = removedOps.valueAt(opNum);
+
+ final int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(attributionNum);
+
+ while (attributedOp.isRunning()) {
+ attributedOp.finished(attributedOp.mInProgressEvents.keyAt(0));
+ }
+ while (attributedOp.isPaused()) {
+ attributedOp.finished(attributedOp.mPausedInProgressEvents.keyAt(0));
+ }
+ }
+ }
+ }
+ }
+
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::clearHistory,
+ mHistoricalRegistry, uid, packageName));
+ }
+
+ @Override
+ public void uidRemoved(int uid) {
+ synchronized (this) {
+ if (mUidStates.indexOfKey(uid) >= 0) {
+ mUidStates.get(uid).clear();
+ mUidStates.remove(uid);
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ // The callback method from ForegroundPolicyInterface
+ private void onUidStateChanged(int uid, int state, boolean foregroundModeMayChange) {
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, true);
+
+ if (uidState != null && foregroundModeMayChange && uidState.hasForegroundWatchers) {
+ for (int fgi = uidState.foregroundOps.size() - 1; fgi >= 0; fgi--) {
+ if (!uidState.foregroundOps.valueAt(fgi)) {
+ continue;
+ }
+ final int code = uidState.foregroundOps.keyAt(fgi);
+
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)
+ && uidState.getUidMode(code) == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChangedForAllPkgsInUid,
+ this, code, uidState.uid, true, null));
+ } else if (uidState.pkgOps != null) {
+ final ArraySet<OnOpModeChangedListener> listenerSet =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (listenerSet != null) {
+ for (int cbi = listenerSet.size() - 1; cbi >= 0; cbi--) {
+ final OnOpModeChangedListener listener = listenerSet.valueAt(cbi);
+ if ((listener.getFlags()
+ & AppOpsManager.WATCH_FOREGROUND_CHANGES) == 0
+ || !listener.isWatchingUid(uidState.uid)) {
+ continue;
+ }
+ for (int pkgi = uidState.pkgOps.size() - 1; pkgi >= 0; pkgi--) {
+ final Op op = uidState.pkgOps.valueAt(pkgi).get(code);
+ if (op == null) {
+ continue;
+ }
+ if (op.getMode() == AppOpsManager.MODE_FOREGROUND) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, listenerSet.valueAt(cbi), code, uidState.uid,
+ uidState.pkgOps.keyAt(pkgi)));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (uidState != null && uidState.pkgOps != null) {
+ int numPkgs = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgs; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+
+ int numOps = ops.size();
+ for (int opNum = 0; opNum < numOps; opNum++) {
+ Op op = ops.valueAt(opNum);
+
+ int numAttributions = op.mAttributions.size();
+ for (int attributionNum = 0; attributionNum < numAttributions;
+ attributionNum++) {
+ AttributedOp attributedOp = op.mAttributions.valueAt(
+ attributionNum);
+
+ attributedOp.onUidStateChanged(state);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Notify the proc state or capability has changed for a certain UID.
+ */
+ @Override
+ public void updateUidProcState(int uid, int procState,
+ @ActivityManager.ProcessCapability int capability) {
+ synchronized (this) {
+ getUidStateTracker().updateUidProcState(uid, procState, capability);
+ if (!mUidStates.contains(uid)) {
+ UidState uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ onUidStateChanged(uid,
+ AppOpsUidStateTracker.processStateToUidState(procState), false);
+ }
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ Slog.w(TAG, "Writing app ops before shutdown...");
+ boolean doWrite = false;
+ synchronized (this) {
+ if (mWriteScheduled) {
+ mWriteScheduled = false;
+ mFastWriteScheduled = false;
+ mHandler.removeCallbacks(mWriteRunner);
+ doWrite = true;
+ }
+ }
+ if (doWrite) {
+ writeState();
+ }
+
+ mHistoricalRegistry.shutdown();
+ }
+
+ private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int j = 0; j < pkgOps.size(); j++) {
+ Op curOp = pkgOps.valueAt(j);
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ } else {
+ for (int j = 0; j < ops.length; j++) {
+ Op curOp = pkgOps.get(ops[j]);
+ if (curOp != null) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(getOpEntryForResult(curOp));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ @Nullable
+ private ArrayList<AppOpsManager.OpEntry> collectUidOps(@NonNull UidState uidState,
+ @Nullable int[] ops) {
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes == null) {
+ return null;
+ }
+
+ int opModeCount = opModes.size();
+ if (opModeCount == 0) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = null;
+ if (ops == null) {
+ resOps = new ArrayList<>();
+ for (int i = 0; i < opModeCount; i++) {
+ int code = opModes.keyAt(i);
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ } else {
+ for (int j = 0; j < ops.length; j++) {
+ int code = ops[j];
+ if (opModes.indexOfKey(code) >= 0) {
+ if (resOps == null) {
+ resOps = new ArrayList<>();
+ }
+ resOps.add(new OpEntry(code, opModes.get(code), Collections.emptyMap()));
+ }
+ }
+ }
+ return resOps;
+ }
+
+ private static @NonNull OpEntry getOpEntryForResult(@NonNull Op op) {
+ return op.createEntryLocked();
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+ final int callingUid = Binder.getCallingUid();
+ final boolean hasAllPackageAccess = mContext.checkPermission(
+ Manifest.permission.GET_APP_OPS_STATS, Binder.getCallingPid(),
+ Binder.getCallingUid(), null) == PackageManager.PERMISSION_GRANTED;
+ ArrayList<AppOpsManager.PackageOps> res = null;
+ synchronized (this) {
+ final int uidStateCount = mUidStates.size();
+ for (int i = 0; i < uidStateCount; i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ if (uidState.pkgOps == null || uidState.pkgOps.isEmpty()) {
+ continue;
+ }
+ ArrayMap<String, Ops> packages = uidState.pkgOps;
+ final int packageCount = packages.size();
+ for (int j = 0; j < packageCount; j++) {
+ Ops pkgOps = packages.valueAt(j);
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps != null) {
+ if (res == null) {
+ res = new ArrayList<>();
+ }
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ // Caller can always see their packages and with a permission all.
+ if (hasAllPackageAccess || callingUid == pkgOps.uidState.uid) {
+ res.add(resPackage);
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops) {
+ enforceGetAppOpsStatsPermissionIfNeeded(uid, packageName);
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return Collections.emptyList();
+ }
+ synchronized (this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null,
+ /* edit */ false);
+ if (pkgOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ pkgOps.packageName, pkgOps.uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void enforceGetAppOpsStatsPermissionIfNeeded(int uid, String packageName) {
+ final int callingUid = Binder.getCallingUid();
+ // We get to access everything
+ if (callingUid == Process.myPid()) {
+ return;
+ }
+ // Apps can access their own data
+ if (uid == callingUid && packageName != null
+ && checkPackage(uid, packageName) == MODE_ALLOWED) {
+ return;
+ }
+ // Otherwise, you need a permission...
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), callingUid, null);
+ }
+
+ /**
+ * Verify that historical appop request arguments are valid.
+ */
+ private void ensureHistoricalOpRequestIsValid(int uid, String packageName,
+ String attributionTag, List<String> opNames, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ Preconditions.checkArgument(uid != Process.INVALID_UID);
+ } else {
+ Preconditions.checkArgument(uid == Process.INVALID_UID);
+ }
+
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ Objects.requireNonNull(packageName);
+ } else {
+ Preconditions.checkArgument(packageName == null);
+ }
+
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) == 0) {
+ Preconditions.checkArgument(attributionTag == null);
+ }
+
+ if ((filter & FILTER_BY_OP_NAMES) != 0) {
+ Objects.requireNonNull(opNames);
+ } else {
+ Preconditions.checkArgument(opNames == null);
+ }
+
+ Preconditions.checkFlagsArgument(filter,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_ATTRIBUTION_TAG
+ | FILTER_BY_OP_NAMES);
+ Preconditions.checkArgumentNonnegative(beginTimeMillis);
+ Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+ Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ }
+
+ @Override
+ public void getHistoricalOps(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback) {
+ PackageManager pm = mContext.getPackageManager();
+
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ ActivityManagerInternal ami = LocalServices.getService(ActivityManagerInternal.class);
+ boolean isSelfRequest = (filter & FILTER_BY_UID) != 0 && uid == Binder.getCallingUid();
+ if (!isSelfRequest) {
+ boolean isCallerInstrumented =
+ ami.getInstrumentationSourceUid(Binder.getCallingUid()) != Process.INVALID_UID;
+ boolean isCallerSystem = Binder.getCallingPid() == Process.myPid();
+ boolean isCallerPermissionController;
+ try {
+ isCallerPermissionController = pm.getPackageUidAsUser(
+ mContext.getPackageManager().getPermissionControllerPackageName(), 0,
+ UserHandle.getUserId(Binder.getCallingUid()))
+ == Binder.getCallingUid();
+ } catch (PackageManager.NameNotFoundException doesNotHappen) {
+ return;
+ }
+
+ boolean doesCallerHavePermission = mContext.checkPermission(
+ android.Manifest.permission.GET_HISTORICAL_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
+
+ if (!isCallerSystem && !isCallerInstrumented && !isCallerPermissionController
+ && !doesCallerHavePermission) {
+ mHandler.post(() -> callback.sendResult(new Bundle()));
+ return;
+ }
+
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+ }
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
+ }
+
+ @Override
+ public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
+ Objects.requireNonNull(callback, "callback cannot be null");
+
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "getHistoricalOps");
+
+ final String[] opNamesArray = (opNames != null)
+ ? opNames.toArray(new String[opNames.size()]) : null;
+
+ Set<String> attributionChainExemptPackages = null;
+ if ((dataType & HISTORY_FLAG_GET_ATTRIBUTION_CHAINS) != 0) {
+ attributionChainExemptPackages =
+ PermissionManager.getIndicatorExemptedPackages(mContext);
+ }
+
+ final String[] chainExemptPkgArray = attributionChainExemptPackages != null
+ ? attributionChainExemptPackages.toArray(
+ new String[attributionChainExemptPackages.size()]) : null;
+
+ // Must not hold the appops lock
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
+ filter, beginTimeMillis, endTimeMillis, flags, chainExemptPkgArray,
+ callback).recycleOnUse());
+ }
+
+ @Override
+ public void reloadNonHistoricalState() {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "reloadNonHistoricalState");
+ writeState();
+ readState();
+ }
+
+ @Override
+ public List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops) {
+ mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.OpEntry> resOps = collectUidOps(uidState, ops);
+ if (resOps == null) {
+ return null;
+ }
+ ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+ AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+ null, uidState.uid, resOps);
+ res.add(resPackage);
+ return res;
+ }
+ }
+
+ private void pruneOpLocked(Op op, int uid, String packageName) {
+ op.removeAttributionsWithNoTime();
+
+ if (op.mAttributions.isEmpty()) {
+ Ops ops = getOpsLocked(uid, packageName, null, false, null, /* edit */ false);
+ if (ops != null) {
+ ops.remove(op.op);
+ op.setMode(AppOpsManager.opToDefaultMode(op.op));
+ if (ops.size() <= 0) {
+ UidState uidState = ops.uidState;
+ ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+ if (pkgOps != null) {
+ pkgOps.remove(ops.packageName);
+ mAppOpsServiceInterface.removePackage(ops.packageName,
+ UserHandle.getUserId(uidState.uid));
+ if (pkgOps.isEmpty()) {
+ uidState.pkgOps = null;
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uid);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid) {
+ if (callingPid == Process.myPid()) {
+ return;
+ }
+ final int callingUser = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ if (mProfileOwners != null && mProfileOwners.get(callingUser, -1) == callingUid) {
+ if (targetUid >= 0 && callingUser == UserHandle.getUserId(targetUid)) {
+ // Profile owners are allowed to change modes but only for apps
+ // within their user.
+ return;
+ }
+ }
+ }
+ mContext.enforcePermission(android.Manifest.permission.MANAGE_APP_OPS_MODES,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ @Override
+ public void setUidMode(int code, int uid, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ if (DEBUG) {
+ Slog.i(TAG, "uid " + uid + " OP_" + opToName(code) + " := " + modeToName(mode)
+ + " by uid " + Binder.getCallingUid());
+ }
+
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ code = AppOpsManager.opToSwitch(code);
+
+ if (permissionPolicyCallback == null) {
+ updatePermissionRevokedCompat(uid, code, mode);
+ }
+
+ int previousMode;
+ synchronized (this) {
+ final int defaultMode = AppOpsManager.opToDefaultMode(code);
+
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState == null) {
+ if (mode == defaultMode) {
+ return;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+ if (uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ previousMode = uidState.getUidMode(code);
+ } else {
+ // doesn't look right but is legacy behavior.
+ previousMode = MODE_DEFAULT;
+ }
+
+ if (!uidState.setUidMode(code, mode)) {
+ return;
+ }
+ uidState.evalForegroundOps();
+ if (mode != MODE_ERRORED && mode != previousMode) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+
+ notifyOpChangedForAllPkgsInUid(code, uid, false, permissionPolicyCallback);
+ notifyOpChangedSync(code, uid, null, mode, previousMode);
+ }
+
+ /**
+ * Notify that an op changed for all packages in an uid.
+ *
+ * @param code The op that changed
+ * @param uid The uid the op was changed for
+ * @param onlyForeground Only notify watchers that watch for foreground changes
+ */
+ private void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
+ @Nullable IAppOpsCallback callbackToIgnore) {
+ ModeCallback listenerToIgnore = callbackToIgnore != null
+ ? mModeWatchers.get(callbackToIgnore.asBinder()) : null;
+ mAppOpsServiceInterface.notifyOpChangedForAllPkgsInUid(code, uid, onlyForeground,
+ listenerToIgnore);
+ }
+
+ private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager == null) {
+ // This can only happen during early boot. At this time the permission state and appop
+ // state are in sync
+ return;
+ }
+
+ String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return;
+ }
+ String packageName = packageNames[0];
+
+ int[] ops = mSwitchedOps.get(switchCode);
+ for (int code : ops) {
+ String permissionName = AppOpsManager.opToPermission(code);
+ if (permissionName == null) {
+ continue;
+ }
+
+ if (packageManager.checkPermission(permissionName, packageName)
+ != PackageManager.PERMISSION_GRANTED) {
+ continue;
+ }
+
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (!permissionInfo.isRuntime()) {
+ continue;
+ }
+
+ boolean supportsRuntimePermissions = getPackageManagerInternal()
+ .getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M;
+
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ boolean isRevokedCompat;
+ if (permissionInfo.backgroundPermission != null) {
+ if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+ == PackageManager.PERMISSION_GRANTED) {
+ boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+
+ if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission="
+ + permissionInfo.backgroundPermission);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+ packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ isBackgroundRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+ && mode != AppOpsManager.MODE_FOREGROUND;
+ } else {
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+ }
+
+ if (isRevokedCompat && supportsRuntimePermissions) {
+ Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+ + " permission state, this is discouraged and you should revoke the"
+ + " runtime permission instead: uid=" + uid + ", switchCode="
+ + switchCode + ", mode=" + mode + ", permission=" + permissionName);
+ }
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionName, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode,
+ int previousMode) {
+ final StorageManagerInternal storageManagerInternal =
+ LocalServices.getService(StorageManagerInternal.class);
+ if (storageManagerInternal != null) {
+ storageManagerInternal.onAppOpsChanged(code, uid, packageName, mode, previousMode);
+ }
+ }
+
+ @Override
+ public void setMode(int code, int uid, @NonNull String packageName, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback) {
+ enforceManageAppOpsModes(Binder.getCallingPid(), Binder.getCallingUid(), uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ ArraySet<OnOpModeChangedListener> repCbs = null;
+ code = AppOpsManager.opToSwitch(code);
+
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot setMode", e);
+ return;
+ }
+
+ int previousMode = MODE_DEFAULT;
+ synchronized (this) {
+ UidState uidState = getUidStateLocked(uid, false);
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ true);
+ if (op != null) {
+ if (op.getMode() != mode) {
+ previousMode = op.getMode();
+ op.setMode(mode);
+
+ if (uidState != null) {
+ uidState.evalForegroundOps();
+ }
+ ArraySet<OnOpModeChangedListener> cbs =
+ mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ cbs = mAppOpsServiceInterface.getPackageModeChangedListeners(packageName);
+ if (cbs != null) {
+ if (repCbs == null) {
+ repCbs = new ArraySet<>();
+ }
+ repCbs.addAll(cbs);
+ }
+ if (repCbs != null && permissionPolicyCallback != null) {
+ repCbs.remove(mModeWatchers.get(permissionPolicyCallback.asBinder()));
+ }
+ if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+ // If going into the default mode, prune this op
+ // if there is nothing else interesting in it.
+ pruneOpLocked(op, uid, packageName);
+ }
+ scheduleFastWriteLocked();
+ if (mode != MODE_ERRORED) {
+ updateStartedOpModeForUidLocked(code, mode == MODE_IGNORED, uid);
+ }
+ }
+ }
+ }
+ if (repCbs != null) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, repCbs, code, uid, packageName));
+ }
+
+ notifyOpChangedSync(code, uid, packageName, mode, previousMode);
+ }
+
+ private void notifyOpChanged(ArraySet<OnOpModeChangedListener> callbacks, int code,
+ int uid, String packageName) {
+ for (int i = 0; i < callbacks.size(); i++) {
+ final OnOpModeChangedListener callback = callbacks.valueAt(i);
+ notifyOpChanged(callback, code, uid, packageName);
+ }
+ }
+
+ private void notifyOpChanged(OnOpModeChangedListener callback, int code,
+ int uid, String packageName) {
+ mAppOpsServiceInterface.notifyOpChanged(callback, code, uid, packageName);
+ }
+
+ private static ArrayList<ChangeRec> addChange(ArrayList<ChangeRec> reports,
+ int op, int uid, String packageName, int previousMode) {
+ boolean duplicate = false;
+ if (reports == null) {
+ reports = new ArrayList<>();
+ } else {
+ final int reportCount = reports.size();
+ for (int j = 0; j < reportCount; j++) {
+ ChangeRec report = reports.get(j);
+ if (report.op == op && report.pkg.equals(packageName)) {
+ duplicate = true;
+ break;
+ }
+ }
+ }
+ if (!duplicate) {
+ reports.add(new ChangeRec(op, uid, packageName, previousMode));
+ }
+
+ return reports;
+ }
+
+ private static HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> addCallbacks(
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks,
+ int op, int uid, String packageName, int previousMode,
+ ArraySet<OnOpModeChangedListener> cbs) {
+ if (cbs == null) {
+ return callbacks;
+ }
+ if (callbacks == null) {
+ callbacks = new HashMap<>();
+ }
+ final int N = cbs.size();
+ for (int i=0; i<N; i++) {
+ OnOpModeChangedListener cb = cbs.valueAt(i);
+ ArrayList<ChangeRec> reports = callbacks.get(cb);
+ ArrayList<ChangeRec> changed = addChange(reports, op, uid, packageName, previousMode);
+ if (changed != reports) {
+ callbacks.put(cb, changed);
+ }
+ }
+ return callbacks;
+ }
+
+ static final class ChangeRec {
+ final int op;
+ final int uid;
+ final String pkg;
+ final int previous_mode;
+
+ ChangeRec(int _op, int _uid, String _pkg, int _previous_mode) {
+ op = _op;
+ uid = _uid;
+ pkg = _pkg;
+ previous_mode = _previous_mode;
+ }
+ }
+
+ @Override
+ public void resetAllModes(int reqUserId, String reqPackageName) {
+ final int callingPid = Binder.getCallingPid();
+ final int callingUid = Binder.getCallingUid();
+ reqUserId = ActivityManager.handleIncomingUser(callingPid, callingUid, reqUserId,
+ true, true, "resetAllModes", null);
+
+ int reqUid = -1;
+ if (reqPackageName != null) {
+ try {
+ reqUid = AppGlobals.getPackageManager().getPackageUid(
+ reqPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, reqUserId);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+
+ enforceManageAppOpsModes(callingPid, callingUid, reqUid);
+
+ HashMap<OnOpModeChangedListener, ArrayList<ChangeRec>> callbacks = null;
+ ArrayList<ChangeRec> allChanges = new ArrayList<>();
+ synchronized (this) {
+ boolean changed = false;
+ for (int i = mUidStates.size() - 1; i >= 0; i--) {
+ UidState uidState = mUidStates.valueAt(i);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && (uidState.uid == reqUid || reqUid == -1)) {
+ final int uidOpCount = opModes.size();
+ for (int j = uidOpCount - 1; j >= 0; j--) {
+ final int code = opModes.keyAt(j);
+ if (AppOpsManager.opAllowsReset(code)) {
+ int previousMode = opModes.valueAt(j);
+ uidState.setUidMode(code, AppOpsManager.opToDefaultMode(code));
+ for (String packageName : getPackagesForUid(uidState.uid)) {
+ callbacks = addCallbacks(callbacks, code, uidState.uid,
+ packageName, previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(code));
+ callbacks = addCallbacks(callbacks, code, uidState.uid,
+ packageName, previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, code, uidState.uid,
+ packageName, previousMode);
+ }
+ }
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+
+ if (reqUserId != UserHandle.USER_ALL
+ && reqUserId != UserHandle.getUserId(uidState.uid)) {
+ // Skip any ops for a different user
+ continue;
+ }
+
+ Map<String, Ops> packages = uidState.pkgOps;
+ Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+ boolean uidChanged = false;
+ while (it.hasNext()) {
+ Map.Entry<String, Ops> ent = it.next();
+ String packageName = ent.getKey();
+ if (reqPackageName != null && !reqPackageName.equals(packageName)) {
+ // Skip any ops for a different package
+ continue;
+ }
+ Ops pkgOps = ent.getValue();
+ for (int j=pkgOps.size()-1; j>=0; j--) {
+ Op curOp = pkgOps.valueAt(j);
+ if (shouldDeferResetOpToDpm(curOp.op)) {
+ deferResetOpToDpm(curOp.op, reqPackageName, reqUserId);
+ continue;
+ }
+ if (AppOpsManager.opAllowsReset(curOp.op)
+ && curOp.getMode() != AppOpsManager.opToDefaultMode(curOp.op)) {
+ int previousMode = curOp.getMode();
+ curOp.setMode(AppOpsManager.opToDefaultMode(curOp.op));
+ changed = true;
+ uidChanged = true;
+ final int uid = curOp.uidState.uid;
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode,
+ mAppOpsServiceInterface.getOpModeChangedListeners(curOp.op));
+ callbacks = addCallbacks(callbacks, curOp.op, uid, packageName,
+ previousMode, mAppOpsServiceInterface
+ .getPackageModeChangedListeners(packageName));
+
+ allChanges = addChange(allChanges, curOp.op, uid, packageName,
+ previousMode);
+ curOp.removeAttributionsWithNoTime();
+ if (curOp.mAttributions.isEmpty()) {
+ pkgOps.removeAt(j);
+ }
+ }
+ }
+ if (pkgOps.size() == 0) {
+ it.remove();
+ mAppOpsServiceInterface.removePackage(packageName,
+ UserHandle.getUserId(uidState.uid));
+ }
+ }
+ if (uidState.isDefault()) {
+ uidState.clear();
+ mUidStates.remove(uidState.uid);
+ }
+ if (uidChanged) {
+ uidState.evalForegroundOps();
+ }
+ }
+
+ if (changed) {
+ scheduleFastWriteLocked();
+ }
+ }
+ if (callbacks != null) {
+ for (Map.Entry<OnOpModeChangedListener, ArrayList<ChangeRec>> ent
+ : callbacks.entrySet()) {
+ OnOpModeChangedListener cb = ent.getKey();
+ ArrayList<ChangeRec> reports = ent.getValue();
+ for (int i=0; i<reports.size(); i++) {
+ ChangeRec rep = reports.get(i);
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChanged,
+ this, cb, rep.op, rep.uid, rep.pkg));
+ }
+ }
+ }
+
+ int numChanges = allChanges.size();
+ for (int i = 0; i < numChanges; i++) {
+ ChangeRec change = allChanges.get(i);
+ notifyOpChangedSync(change.op, change.uid, change.pkg,
+ AppOpsManager.opToDefaultMode(change.op), change.previous_mode);
+ }
+ }
+
+ private boolean shouldDeferResetOpToDpm(int op) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ return dpmi != null && dpmi.supportsResetOp(op);
+ }
+
+ /** Assumes {@link #shouldDeferResetOpToDpm(int)} is true. */
+ private void deferResetOpToDpm(int op, String packageName, @UserIdInt int userId) {
+ // TODO(b/174582385): avoid special-casing app-op resets by migrating app-op permission
+ // pre-grants to a role-based mechanism or another general-purpose mechanism.
+ dpmi.resetOp(op, packageName, userId);
+ }
+
+ private void evalAllForegroundOpsLocked() {
+ for (int uidi = mUidStates.size() - 1; uidi >= 0; uidi--) {
+ final UidState uidState = mUidStates.valueAt(uidi);
+ if (uidState.foregroundOps != null) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingModeWithFlags(int op, String packageName, int flags,
+ IAppOpsCallback callback) {
+ int watchedUid = -1;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ // TODO: should have a privileged permission to protect this.
+ // Also, if the caller has requested WATCH_FOREGROUND_CHANGES, should we require
+ // the USAGE_STATS permission since this can provide information about when an
+ // app is in the foreground?
+ Preconditions.checkArgumentInRange(op, AppOpsManager.OP_NONE,
+ AppOpsManager._NUM_OP - 1, "Invalid op code: " + op);
+ if (callback == null) {
+ return;
+ }
+ final boolean mayWatchPackageName = packageName != null
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(callingUid));
+ synchronized (this) {
+ int switchOp = (op != AppOpsManager.OP_NONE) ? AppOpsManager.opToSwitch(op) : op;
+
+ int notifiedOps;
+ if ((flags & CALL_BACK_ON_SWITCHED_OP) == 0) {
+ if (op == OP_NONE) {
+ notifiedOps = ALL_OPS;
+ } else {
+ notifiedOps = op;
+ }
+ } else {
+ notifiedOps = switchOp;
+ }
+
+ ModeCallback cb = mModeWatchers.get(callback.asBinder());
+ if (cb == null) {
+ cb = new ModeCallback(callback, watchedUid, flags, notifiedOps, callingUid,
+ callingPid);
+ mModeWatchers.put(callback.asBinder(), cb);
+ }
+ if (switchOp != AppOpsManager.OP_NONE) {
+ mAppOpsServiceInterface.startWatchingOpModeChanged(cb, switchOp);
+ }
+ if (mayWatchPackageName) {
+ mAppOpsServiceInterface.startWatchingPackageModeChanged(cb, packageName);
+ }
+ evalAllForegroundOpsLocked();
+ }
+ }
+
+ @Override
+ public void stopWatchingMode(IAppOpsCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ ModeCallback cb = mModeWatchers.remove(callback.asBinder());
+ if (cb != null) {
+ cb.unlinkToDeath();
+ mAppOpsServiceInterface.removeListener(cb);
+ }
+
+ evalAllForegroundOpsLocked();
+ }
+ }
+
+ @Override
+ public int checkOperation(int code, int uid, String packageName,
+ @Nullable String attributionTag, boolean raw) {
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag, raw);
+ }
+
+ /**
+ * Get the mode of an app-op.
+ *
+ * @param code The code of the op
+ * @param uid The uid of the package the op belongs to
+ * @param packageName The package the op belongs to
+ * @param raw If the raw state of eval-ed state should be checked.
+ * @return The mode of the op
+ */
+ private @Mode int checkOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean raw) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, null);
+ } catch (SecurityException e) {
+ Slog.e(TAG, "checkOperation", e);
+ return AppOpsManager.opToDefaultMode(code);
+ }
+
+ if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ synchronized (this) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, true)) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ code = AppOpsManager.opToSwitch(code);
+ UidState uidState = getUidStateLocked(uid, false);
+ if (uidState != null
+ && uidState.getUidMode(code) != AppOpsManager.opToDefaultMode(code)) {
+ final int rawMode = uidState.getUidMode(code);
+ return raw ? rawMode : uidState.evalMode(code, rawMode);
+ }
+ Op op = getOpLocked(code, uid, packageName, null, false, pvr.bypass, /* edit */ false);
+ if (op == null) {
+ return AppOpsManager.opToDefaultMode(code);
+ }
+ return raw ? op.getMode() : op.uidState.evalMode(op.op, op.getMode());
+ }
+ }
+
+ @Override
+ public int checkPackage(int uid, String packageName) {
+ Objects.requireNonNull(packageName);
+ try {
+ verifyAndGetBypass(uid, packageName, null);
+ // When the caller is the system, it's possible that the packageName is the special
+ // one (e.g., "root") which isn't actually existed.
+ if (resolveUid(packageName) == uid
+ || (isPackageExisted(packageName)
+ && !filterAppAccessUnlocked(packageName, UserHandle.getUserId(uid)))) {
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ return AppOpsManager.MODE_ERRORED;
+ } catch (SecurityException ignored) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+ }
+
+ private boolean isPackageExisted(String packageName) {
+ return getPackageManagerInternal().getPackageStateInternal(packageName) != null;
+ }
+
+ /**
+ * This method will check with PackageManager to determine if the package provided should
+ * be visible to the {@link Binder#getCallingUid()}.
+ *
+ * NOTE: This must not be called while synchronized on {@code this} to avoid dead locks
+ */
+ private boolean filterAppAccessUnlocked(String packageName, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ return LocalServices.getService(PackageManagerInternal.class)
+ .filterAppAccess(packageName, callingUid, userId);
+ }
+
+ @Override
+ public int noteOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+ return noteOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ Process.INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ }
+
+ @Override
+ public int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @OpFlags int flags) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "noteOperation", e);
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + "flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ if (attributedOp.isRunning()) {
+ Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName + " code "
+ + code + " startTime of in progress event="
+ + attributedOp.mInProgressEvents.valueAt(0).getStartTime());
+ }
+
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ final UidState uidState = ops.uidState;
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass, false)) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_IGNORED);
+ return AppOpsManager.MODE_IGNORED;
+ }
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (uidMode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ uidMode);
+ return uidMode;
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (DEBUG) {
+ Slog.d(TAG, "noteOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ mode);
+ return mode;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG,
+ "noteOperation: allowing code " + code + " uid " + uid + " package "
+ + packageName + (attributionTag == null ? ""
+ : "." + attributionTag) + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ AppOpsManager.MODE_ALLOWED);
+ attributedOp.accessed(proxyUid, proxyPackageName, proxyAttributionTag,
+ uidState.getState(),
+ flags);
+
+ return AppOpsManager.MODE_ALLOWED;
+ }
+ }
+
+ @Override
+ public boolean isAttributionTagValid(int uid, @NonNull String packageName,
+ @Nullable String attributionTag,
+ @Nullable String proxyPackageName) {
+ try {
+ return verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName)
+ .isAttributionTagValid;
+ } catch (SecurityException ignored) {
+ // We don't want to throw, this exception will be handled in the (c/n/s)Operation calls
+ // when they need the bypass object.
+ return false;
+ }
+ }
+
+ // TODO moltmann: Allow watching for attribution ops
+ @Override
+ public void startWatchingActive(int[] ops, IAppOpsActiveCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ if (ops != null) {
+ Preconditions.checkArrayElementsInRange(ops, 0,
+ AppOpsManager._NUM_OP - 1, "Invalid op code in: " + Arrays.toString(ops));
+ }
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ SparseArray<ActiveCallback> callbacks = mActiveWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mActiveWatchers.put(callback.asBinder(), callbacks);
+ }
+ final ActiveCallback activeCallback = new ActiveCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, activeCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingActive(IAppOpsActiveCallback callback) {
+ if (callback == null) {
+ return;
+ }
+ synchronized (this) {
+ final SparseArray<ActiveCallback> activeCallbacks =
+ mActiveWatchers.remove(callback.asBinder());
+ if (activeCallbacks == null) {
+ return;
+ }
+ final int callbackCount = activeCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ activeCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ SparseArray<StartedCallback> callbacks = mStartedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mStartedWatchers.put(callback.asBinder(), callbacks);
+ }
+
+ final StartedCallback startedCallback = new StartedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, startedCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingStarted(IAppOpsStartedCallback callback) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+
+ synchronized (this) {
+ final SparseArray<StartedCallback> startedCallbacks =
+ mStartedWatchers.remove(callback.asBinder());
+ if (startedCallbacks == null) {
+ return;
+ }
+
+ final int callbackCount = startedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ startedCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback) {
+ int watchedUid = Process.INVALID_UID;
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ watchedUid = callingUid;
+ }
+ Preconditions.checkArgument(!ArrayUtils.isEmpty(ops), "Ops cannot be null or empty");
+ Preconditions.checkArrayElementsInRange(ops, 0, AppOpsManager._NUM_OP - 1,
+ "Invalid op code in: " + Arrays.toString(ops));
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ SparseArray<NotedCallback> callbacks = mNotedWatchers.get(callback.asBinder());
+ if (callbacks == null) {
+ callbacks = new SparseArray<>();
+ mNotedWatchers.put(callback.asBinder(), callbacks);
+ }
+ final NotedCallback notedCallback = new NotedCallback(callback, watchedUid,
+ callingUid, callingPid);
+ for (int op : ops) {
+ callbacks.put(op, notedCallback);
+ }
+ }
+ }
+
+ @Override
+ public void stopWatchingNoted(IAppOpsNotedCallback callback) {
+ Objects.requireNonNull(callback, "Callback cannot be null");
+ synchronized (this) {
+ final SparseArray<NotedCallback> notedCallbacks =
+ mNotedWatchers.remove(callback.asBinder());
+ if (notedCallbacks == null) {
+ return;
+ }
+ final int callbackCount = notedCallbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ notedCallbacks.valueAt(i).destroy();
+ }
+ }
+ }
+
+ @Override
+ public int startOperation(@NonNull IBinder clientId, int code, int uid,
+ @Nullable String packageName, @Nullable String attributionTag,
+ boolean startIfModeDefault, @NonNull String message,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return AppOpsManager.MODE_IGNORED;
+ }
+
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
+ // purposes and not as a check, also make sure that the caller is allowed to access
+ // the data gated by OP_RECORD_AUDIO.
+ //
+ // TODO: Revert this change before Android 12.
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
+ int result = checkOperation(OP_RECORD_AUDIO, uid, packageName, null, false);
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return result;
+ }
+ }
+ return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
+ Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
+ attributionFlags, attributionChainId, /*dryRun*/ false);
+ }
+
+ private boolean shouldStartForMode(int mode, boolean startIfModeDefault) {
+ return (mode == MODE_ALLOWED || (mode == MODE_DEFAULT && startIfModeDefault));
+ }
+
+ @Override
+ public int startOperationUnchecked(IBinder clientId, int code, int uid,
+ @NonNull String packageName, @Nullable String attributionTag, int proxyUid,
+ String proxyPackageName, @Nullable String proxyAttributionTag, @OpFlags int flags,
+ boolean startIfModeDefault, @AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "startOperation", e);
+ return AppOpsManager.MODE_ERRORED;
+ }
+
+ boolean isRestricted;
+ int startType = START_TYPE_FAILED;
+ synchronized (this) {
+ final Ops ops = getOpsLocked(uid, packageName, attributionTag,
+ pvr.isAttributionTagValid, pvr.bypass, /* edit */ true);
+ if (ops == null) {
+ if (!dryRun) {
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, AppOpsManager.MODE_IGNORED, startType, attributionFlags,
+ attributionChainId);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+ + " package " + packageName + " flags: "
+ + AppOpsManager.flagsToString(flags));
+ }
+ return AppOpsManager.MODE_ERRORED;
+ }
+ final Op op = getOpLocked(ops, code, uid, true);
+ final AttributedOp attributedOp = op.getOrCreateAttribution(op, attributionTag);
+ final UidState uidState = ops.uidState;
+ isRestricted = isOpRestrictedLocked(uid, code, packageName, attributionTag, pvr.bypass,
+ false);
+ final int switchCode = AppOpsManager.opToSwitch(code);
+ // If there is a non-default per UID policy (we set UID op mode only if
+ // non-default) it takes over, otherwise use the per package policy.
+ if (uidState.getUidMode(switchCode) != AppOpsManager.opToDefaultMode(switchCode)) {
+ final int uidMode = uidState.evalMode(code, uidState.getUidMode(switchCode));
+ if (!shouldStartForMode(uidMode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: uid reject #" + uidMode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, uidMode, startType, attributionFlags, attributionChainId);
+ }
+ return uidMode;
+ }
+ } else {
+ final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, uid, true)
+ : op;
+ final int mode = switchOp.uidState.evalMode(switchOp.op, switchOp.getMode());
+ if (!shouldStartForMode(mode, startIfModeDefault)) {
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: reject #" + mode + " for code "
+ + switchCode + " (" + code + ") uid " + uid + " package "
+ + packageName + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ attributedOp.rejected(uidState.getState(), flags);
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag,
+ flags, mode, startType, attributionFlags, attributionChainId);
+ }
+ return mode;
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+ + " package " + packageName + " restricted: " + isRestricted
+ + " flags: " + AppOpsManager.flagsToString(flags));
+ }
+ if (!dryRun) {
+ try {
+ if (isRestricted) {
+ attributedOp.createPaused(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ } else {
+ attributedOp.started(clientId, proxyUid, proxyPackageName,
+ proxyAttributionTag, uidState.getState(), flags,
+ attributionFlags, attributionChainId);
+ startType = START_TYPE_STARTED;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ scheduleOpStartedIfNeededLocked(code, uid, packageName, attributionTag, flags,
+ isRestricted ? MODE_IGNORED : MODE_ALLOWED, startType, attributionFlags,
+ attributionChainId);
+ }
+ }
+
+ // Possible bug? The raw mode could have been MODE_DEFAULT to reach here.
+ return isRestricted ? MODE_IGNORED : MODE_ALLOWED;
+ }
+
+ @Override
+ public void finishOperation(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
+ verifyIncomingUid(uid);
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return;
+ }
+
+ String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return;
+ }
+
+ finishOperationUnchecked(clientId, code, uid, resolvedPackageName, attributionTag);
+ }
+
+ @Override
+ public void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+ if (!pvr.isAttributionTagValid) {
+ attributionTag = null;
+ }
+ } catch (SecurityException e) {
+ Slog.e(TAG, "Cannot finishOperation", e);
+ return;
+ }
+
+ synchronized (this) {
+ Op op = getOpLocked(code, uid, packageName, attributionTag, pvr.isAttributionTagValid,
+ pvr.bypass, /* edit */ true);
+ if (op == null) {
+ Slog.e(TAG, "Operation not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp == null) {
+ Slog.e(TAG, "Attribution not found: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ return;
+ }
+
+ if (attributedOp.isRunning() || attributedOp.isPaused()) {
+ attributedOp.finished(clientId);
+ } else {
+ Slog.e(TAG, "Operation not started: uid=" + uid + " pkg=" + packageName + "("
+ + attributionTag + ") op=" + AppOpsManager.opToName(code));
+ }
+ }
+ }
+
+ void scheduleOpActiveChangedIfNeededLocked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean active,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ ArraySet<ActiveCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mActiveWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<ActiveCallback> callbacks = mActiveWatchers.valueAt(i);
+ ActiveCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpActiveChanged,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, active,
+ attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpActiveChanged(ArraySet<ActiveCallback> callbacks,
+ int code, int uid, @NonNull String packageName, @Nullable String attributionTag,
+ boolean active, @AttributionFlags int attributionFlags, int attributionChainId) {
+ // There are features watching for mode changes such as window manager
+ // and location manager which are in our process. The callbacks in these
+ // features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final ActiveCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opActiveChanged(code, uid, packageName, attributionTag,
+ active, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ void scheduleOpStartedIfNeededLocked(int code, int uid, String pkgName,
+ String attributionTag, @OpFlags int flags, @Mode int result,
+ @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ ArraySet<StartedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mStartedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<StartedCallback> callbacks = mStartedWatchers.valueAt(i);
+
+ StartedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpStarted,
+ this, dispatchedCallbacks, code, uid, pkgName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId));
+ }
+
+ private void notifyOpStarted(ArraySet<StartedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result, @AppOpsManager.OnOpStartedListener.StartedType int startedType,
+ @AttributionFlags int attributionFlags, int attributionChainId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final StartedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opStarted(code, uid, packageName, attributionTag, flags,
+ result, startedType, attributionFlags, attributionChainId);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void scheduleOpNotedIfNeededLocked(int code, int uid, String packageName,
+ String attributionTag, @OpFlags int flags, @Mode int result) {
+ ArraySet<NotedCallback> dispatchedCallbacks = null;
+ final int callbackListCount = mNotedWatchers.size();
+ for (int i = 0; i < callbackListCount; i++) {
+ final SparseArray<NotedCallback> callbacks = mNotedWatchers.valueAt(i);
+ final NotedCallback callback = callbacks.get(code);
+ if (callback != null) {
+ if (callback.mWatchingUid >= 0 && callback.mWatchingUid != uid) {
+ continue;
+ }
+ if (dispatchedCallbacks == null) {
+ dispatchedCallbacks = new ArraySet<>();
+ }
+ dispatchedCallbacks.add(callback);
+ }
+ }
+ if (dispatchedCallbacks == null) {
+ return;
+ }
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyOpChecked,
+ this, dispatchedCallbacks, code, uid, packageName, attributionTag, flags,
+ result));
+ }
+
+ private void notifyOpChecked(ArraySet<NotedCallback> callbacks,
+ int code, int uid, String packageName, String attributionTag, @OpFlags int flags,
+ @Mode int result) {
+ // There are features watching for checks in our process. The callbacks in
+ // these features may require permissions our remote caller does not have.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final int callbackCount = callbacks.size();
+ for (int i = 0; i < callbackCount; i++) {
+ final NotedCallback callback = callbacks.valueAt(i);
+ try {
+ if (shouldIgnoreCallback(code, callback.mCallingPid, callback.mCallingUid)) {
+ continue;
+ }
+ callback.mCallback.opNoted(code, uid, packageName, attributionTag, flags,
+ result);
+ } catch (RemoteException e) {
+ /* do nothing */
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private void verifyIncomingUid(int uid) {
+ if (uid == Binder.getCallingUid()) {
+ return;
+ }
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+
+ private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
+ // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
+ // as watcher should not use this to signal if the value is changed.
+ return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
+ watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void verifyIncomingOp(int op) {
+ if (op >= 0 && op < AppOpsManager._NUM_OP) {
+ // Enforce manage appops permission if it's a restricted read op.
+ if (opRestrictsRead(op)) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
+ Binder.getCallingPid(), Binder.getCallingUid(), "verifyIncomingOp");
+ }
+ return;
+ }
+ throw new IllegalArgumentException("Bad operation #" + op);
+ }
+
+ private boolean isIncomingPackageValid(@Nullable String packageName, @UserIdInt int userId) {
+ final int callingUid = Binder.getCallingUid();
+ // Handle the special UIDs that don't have actual packages (audioserver, cameraserver, etc).
+ if (packageName == null || isSpecialPackage(callingUid, packageName)) {
+ return true;
+ }
+
+ // If the package doesn't exist, #verifyAndGetBypass would throw a SecurityException in
+ // the end. Although that exception would be caught and return, we could make it return
+ // early.
+ if (!isPackageExisted(packageName)) {
+ return false;
+ }
+
+ if (getPackageManagerInternal().filterAppAccess(packageName, callingUid, userId)) {
+ Slog.w(TAG, packageName + " not found from " + callingUid);
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean isSpecialPackage(int callingUid, @Nullable String packageName) {
+ final String resolvedPackage = AppOpsManager.resolvePackageName(callingUid, packageName);
+ return callingUid == Process.SYSTEM_UID
+ || resolveUid(resolvedPackage) != Process.INVALID_UID;
+ }
+
+ private @Nullable UidState getUidStateLocked(int uid, boolean edit) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState = new UidState(uid);
+ mUidStates.put(uid, uidState);
+ }
+
+ return uidState;
+ }
+
+ @Override
+ public void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible) {
+ synchronized (this) {
+ getUidStateTracker().updateAppWidgetVisibility(uidPackageNames, visible);
+ }
+ }
+
+ /**
+ * @return {@link PackageManagerInternal}
+ */
+ private @NonNull PackageManagerInternal getPackageManagerInternal() {
+ if (mPackageManagerInternal == null) {
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public void verifyPackage(int uid, String packageName) {
+ verifyAndGetBypass(uid, packageName, null);
+ }
+
+ /**
+ * Create a restriction description matching the properties of the package.
+ *
+ * @param pkg The package to create the restriction description for
+ * @return The restriction matching the package
+ */
+ private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
+ return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
+ mContext.checkPermission(android.Manifest.permission
+ .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * @see #verifyAndGetBypass(int, String, String, String)
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag) {
+ return verifyAndGetBypass(uid, packageName, attributionTag, null);
+ }
+
+ /**
+ * Verify that package belongs to uid and return the {@link RestrictionBypass bypass
+ * description} for the package, along with a boolean indicating whether the attribution tag is
+ * valid.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The package the might belong to the uid
+ * @param attributionTag attribution tag or {@code null} if no need to verify
+ * @param proxyPackageName The proxy package, from which the attribution tag is to be pulled
+ * @return PackageVerificationResult containing {@link RestrictionBypass} and whether the
+ * attribution tag is valid
+ */
+ private @NonNull PackageVerificationResult verifyAndGetBypass(int uid, String packageName,
+ @Nullable String attributionTag, @Nullable String proxyPackageName) {
+ if (uid == Process.ROOT_UID) {
+ // For backwards compatibility, don't check package name for root UID.
+ return new PackageVerificationResult(null,
+ /* isAttributionTagValid */ true);
+ }
+ if (Process.isSdkSandboxUid(uid)) {
+ // SDK sandbox processes run in their own UID range, but their associated
+ // UID for checks should always be the UID of the package implementing SDK sandbox
+ // service.
+ // TODO: We will need to modify the callers of this function instead, so
+ // modifications and checks against the app ops state are done with the
+ // correct UID.
+ try {
+ final PackageManager pm = mContext.getPackageManager();
+ final String supplementalPackageName = pm.getSdkSandboxPackageName();
+ if (Objects.equals(packageName, supplementalPackageName)) {
+ uid = pm.getPackageUidAsUser(supplementalPackageName,
+ PackageManager.PackageInfoFlags.of(0), UserHandle.getUserId(uid));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // Shouldn't happen for the supplemental package
+ e.printStackTrace();
+ }
+ }
+
+
+ // Do not check if uid/packageName/attributionTag is already known.
+ synchronized (this) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState != null && uidState.pkgOps != null) {
+ Ops ops = uidState.pkgOps.get(packageName);
+
+ if (ops != null && (attributionTag == null || ops.knownAttributionTags.contains(
+ attributionTag)) && ops.bypass != null) {
+ return new PackageVerificationResult(ops.bypass,
+ ops.validAttributionTags.contains(attributionTag));
+ }
+ }
+ }
+
+ int callingUid = Binder.getCallingUid();
+
+ // Allow any attribution tag for resolvable uids
+ int pkgUid;
+ if (Objects.equals(packageName, "com.android.shell")) {
+ // Special case for the shell which is a package but should be able
+ // to bypass app attribution tag restrictions.
+ pkgUid = Process.SHELL_UID;
+ } else {
+ pkgUid = resolveUid(packageName);
+ }
+ if (pkgUid != Process.INVALID_UID) {
+ if (pkgUid != UserHandle.getAppId(uid)) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid "
+ + UserHandle.getAppId(uid) + otherUidMessage);
+ }
+ return new PackageVerificationResult(RestrictionBypass.UNRESTRICTED,
+ /* isAttributionTagValid */ true);
+ }
+
+ int userId = UserHandle.getUserId(uid);
+ RestrictionBypass bypass = null;
+ boolean isAttributionTagValid = false;
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ if (pkg != null) {
+ isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
+ pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
+ bypass = getBypassforPackage(pkg);
+ }
+ if (!isAttributionTagValid) {
+ AndroidPackage proxyPkg = proxyPackageName != null
+ ? pmInt.getPackage(proxyPackageName) : null;
+ // Re-check in proxy.
+ isAttributionTagValid = isAttributionInPackage(proxyPkg, attributionTag);
+ String msg;
+ if (pkg != null && isAttributionTagValid) {
+ msg = "attributionTag " + attributionTag + " declared in manifest of the proxy"
+ + " package " + proxyPackageName + ", this is not advised";
+ } else if (pkg != null) {
+ msg = "attributionTag " + attributionTag + " not declared in manifest of "
+ + packageName;
+ } else {
+ msg = "package " + packageName + " not found, can't check for "
+ + "attributionTag " + attributionTag;
+ }
+
+ try {
+ if (!mPlatformCompat.isChangeEnabledByPackageName(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE, packageName,
+ userId) || !mPlatformCompat.isChangeEnabledByUid(
+ SECURITY_EXCEPTION_ON_INVALID_ATTRIBUTION_TAG_CHANGE,
+ callingUid)) {
+ // Do not override tags if overriding is not enabled for this package
+ isAttributionTagValid = true;
+ }
+ Slog.e(TAG, msg);
+ } catch (RemoteException neverHappens) {
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+
+ if (pkgUid != uid) {
+ Slog.e(TAG, "Bad call made by uid " + callingUid + ". "
+ + "Package \"" + packageName + "\" does not belong to uid " + uid + ".");
+ String otherUidMessage = DEBUG ? " but it is really " + pkgUid : " but it is not";
+ throw new SecurityException("Specified package \"" + packageName + "\" under uid " + uid
+ + otherUidMessage);
+ }
+
+ return new PackageVerificationResult(bypass, isAttributionTagValid);
+ }
+
+ private boolean isAttributionInPackage(@Nullable AndroidPackage pkg,
+ @Nullable String attributionTag) {
+ if (pkg == null) {
+ return false;
+ } else if (attributionTag == null) {
+ return true;
+ }
+ if (pkg.getAttributions() != null) {
+ int numAttributions = pkg.getAttributions().size();
+ for (int i = 0; i < numAttributions; i++) {
+ if (pkg.getAttributions().get(i).getTag().equals(attributionTag)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get (and potentially create) ops.
+ *
+ * @param uid The uid the package belongs to
+ * @param packageName The name of the package
+ * @param attributionTag attribution tag
+ * @param isAttributionTagValid whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit
+ * == false)
+ * @param edit If an ops does not exist, create the ops?
+ * @return The ops
+ */
+ private Ops getOpsLocked(int uid, String packageName, @Nullable String attributionTag,
+ boolean isAttributionTagValid, @Nullable RestrictionBypass bypass, boolean edit) {
+ UidState uidState = getUidStateLocked(uid, edit);
+ if (uidState == null) {
+ return null;
+ }
+
+ if (uidState.pkgOps == null) {
+ if (!edit) {
+ return null;
+ }
+ uidState.pkgOps = new ArrayMap<>();
+ }
+
+ Ops ops = uidState.pkgOps.get(packageName);
+ if (ops == null) {
+ if (!edit) {
+ return null;
+ }
+ ops = new Ops(packageName, uidState);
+ uidState.pkgOps.put(packageName, ops);
+ }
+
+ if (edit) {
+ if (bypass != null) {
+ ops.bypass = bypass;
+ }
+
+ if (attributionTag != null) {
+ ops.knownAttributionTags.add(attributionTag);
+ if (isAttributionTagValid) {
+ ops.validAttributionTags.add(attributionTag);
+ } else {
+ ops.validAttributionTags.remove(attributionTag);
+ }
+ }
+ }
+
+ return ops;
+ }
+
+ @Override
+ public void scheduleWriteLocked() {
+ if (!mWriteScheduled) {
+ mWriteScheduled = true;
+ mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+ }
+ }
+
+ @Override
+ public void scheduleFastWriteLocked() {
+ if (!mFastWriteScheduled) {
+ mWriteScheduled = true;
+ mFastWriteScheduled = true;
+ mHandler.removeCallbacks(mWriteRunner);
+ mHandler.postDelayed(mWriteRunner, 10 * 1000);
+ }
+ }
+
+ /**
+ * Get the state of an op for a uid.
+ *
+ * @param code The code of the op
+ * @param uid The uid the of the package
+ * @param packageName The package name for which to get the state for
+ * @param attributionTag The attribution tag
+ * @param isAttributionTagValid Whether the given attribution tag is valid
+ * @param bypass When to bypass certain op restrictions (can be null if edit
+ * == false)
+ * @param edit Iff {@code true} create the {@link Op} object if not yet created
+ * @return The {@link Op state} of the op
+ */
+ private @Nullable Op getOpLocked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, boolean isAttributionTagValid,
+ @Nullable RestrictionBypass bypass, boolean edit) {
+ Ops ops = getOpsLocked(uid, packageName, attributionTag, isAttributionTagValid, bypass,
+ edit);
+ if (ops == null) {
+ return null;
+ }
+ return getOpLocked(ops, code, uid, edit);
+ }
+
+ private Op getOpLocked(Ops ops, int code, int uid, boolean edit) {
+ Op op = ops.get(code);
+ if (op == null) {
+ if (!edit) {
+ return null;
+ }
+ op = new Op(ops.uidState, ops.packageName, code, uid);
+ ops.put(code, op);
+ }
+ if (edit) {
+ scheduleWriteLocked();
+ }
+ return op;
+ }
+
+ private boolean isOpRestrictedDueToSuspend(int code, String packageName, int uid) {
+ if (!ArrayUtils.contains(OPS_RESTRICTED_ON_SUSPEND, code)) {
+ return false;
+ }
+ final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ return pmi.isPackageSuspended(packageName, UserHandle.getUserId(uid));
+ }
+
+ private boolean isOpRestrictedLocked(int uid, int code, String packageName,
+ String attributionTag, @Nullable RestrictionBypass appBypass, boolean isCheckOp) {
+ int restrictionSetCount = mOpGlobalRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code)) {
+ return true;
+ }
+ }
+
+ int userHandle = UserHandle.getUserId(uid);
+ restrictionSetCount = mOpUserRestrictions.size();
+
+ for (int i = 0; i < restrictionSetCount; i++) {
+ // For each client, check that the given op is not restricted, or that the given
+ // package is exempt from the restriction.
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.valueAt(i);
+ if (restrictionState.hasRestriction(code, packageName, attributionTag, userHandle,
+ isCheckOp)) {
+ RestrictionBypass opBypass = opAllowSystemBypassRestriction(code);
+ if (opBypass != null) {
+ // If we are the system, bypass user restrictions for certain codes
+ synchronized (this) {
+ if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) {
+ return false;
+ }
+ if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) {
+ return false;
+ }
+ if (opBypass.isRecordAudioRestrictionExcept && appBypass != null
+ && appBypass.isRecordAudioRestrictionExcept) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void readState() {
+ int oldVersion = NO_VERSION;
+ synchronized (mFile) {
+ synchronized (this) {
+ FileInputStream stream;
+ try {
+ stream = mFile.openRead();
+ } catch (FileNotFoundException e) {
+ Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+ return;
+ }
+ boolean success = false;
+ mUidStates.clear();
+ mAppOpsServiceInterface.clearAllModes();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(stream);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Parse next until we reach the start or end
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new IllegalStateException("no start tag found");
+ }
+
+ oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+
+ int outerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("pkg")) {
+ readPackage(parser);
+ } else if (tagName.equals("uid")) {
+ readUidOps(parser);
+ } else {
+ Slog.w(TAG, "Unknown element under <app-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ success = true;
+ } catch (IllegalStateException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NullPointerException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (NumberFormatException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (XmlPullParserException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } catch (IndexOutOfBoundsException e) {
+ Slog.w(TAG, "Failed parsing " + e);
+ } finally {
+ if (!success) {
+ mUidStates.clear();
+ mAppOpsServiceInterface.clearAllModes();
+ }
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+ synchronized (this) {
+ upgradeLocked(oldVersion);
+ }
+ }
+
+ private void upgradeRunAnyInBackgroundLocked() {
+ for (int i = 0; i < mUidStates.size(); i++) {
+ final UidState uidState = mUidStates.valueAt(i);
+ if (uidState == null) {
+ continue;
+ }
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null) {
+ final int idx = opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (idx >= 0) {
+ uidState.setUidMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ opModes.valueAt(idx));
+ }
+ }
+ if (uidState.pkgOps == null) {
+ continue;
+ }
+ boolean changed = false;
+ for (int j = 0; j < uidState.pkgOps.size(); j++) {
+ Ops ops = uidState.pkgOps.valueAt(j);
+ if (ops != null) {
+ final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND);
+ if (op != null && op.getMode() != AppOpsManager.opToDefaultMode(op.op)) {
+ final Op copy = new Op(op.uidState, op.packageName,
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.uid);
+ copy.setMode(op.getMode());
+ ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy);
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ uidState.evalForegroundOps();
+ }
+ }
+ }
+
+ private void upgradeLocked(int oldVersion) {
+ if (oldVersion >= CURRENT_VERSION) {
+ return;
+ }
+ Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
+ switch (oldVersion) {
+ case NO_VERSION:
+ upgradeRunAnyInBackgroundLocked();
+ // fall through
+ case 1:
+ // for future upgrades
+ }
+ scheduleFastWriteLocked();
+ }
+
+ private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
+ XmlPullParserException, IOException {
+ final int uid = parser.getAttributeInt(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ final int code = parser.getAttributeInt(null, "n");
+ final int mode = parser.getAttributeInt(null, "m");
+ setUidMode(code, uid, mode, null);
+ } else {
+ Slog.w(TAG, "Unknown element under <uid-ops>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readPackage(TypedXmlPullParser parser)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ String pkgName = parser.getAttributeValue(null, "n");
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("uid")) {
+ readUid(parser, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ private void readUid(TypedXmlPullParser parser, String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int uid = parser.getAttributeInt(null, "n");
+ final UidState uidState = getUidStateLocked(uid, true);
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("op")) {
+ readOp(parser, uidState, pkgName);
+ } else {
+ Slog.w(TAG, "Unknown element under <pkg>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ uidState.evalForegroundOps();
+ }
+
+ private void readAttributionOp(TypedXmlPullParser parser, @NonNull Op parent,
+ @Nullable String attribution)
+ throws NumberFormatException, IOException, XmlPullParserException {
+ final AttributedOp attributedOp = parent.getOrCreateAttribution(parent, attribution);
+
+ final long key = parser.getAttributeLong(null, "n");
+ final int uidState = extractUidStateFromKey(key);
+ final int opFlags = extractFlagsFromKey(key);
+
+ final long accessTime = parser.getAttributeLong(null, "t", 0);
+ final long rejectTime = parser.getAttributeLong(null, "r", 0);
+ final long accessDuration = parser.getAttributeLong(null, "d", -1);
+ final String proxyPkg = XmlUtils.readStringAttribute(parser, "pp");
+ final int proxyUid = parser.getAttributeInt(null, "pu", Process.INVALID_UID);
+ final String proxyAttributionTag = XmlUtils.readStringAttribute(parser, "pc");
+
+ if (accessTime > 0) {
+ attributedOp.accessed(accessTime, accessDuration, proxyUid, proxyPkg,
+ proxyAttributionTag, uidState, opFlags);
+ }
+ if (rejectTime > 0) {
+ attributedOp.rejected(rejectTime, uidState, opFlags);
+ }
+ }
+
+ private void readOp(TypedXmlPullParser parser,
+ @NonNull UidState uidState, @NonNull String pkgName)
+ throws NumberFormatException, XmlPullParserException, IOException {
+ int opCode = parser.getAttributeInt(null, "n");
+ Op op = new Op(uidState, pkgName, opCode, uidState.uid);
+
+ final int mode = parser.getAttributeInt(null, "m", AppOpsManager.opToDefaultMode(op.op));
+ op.setMode(mode);
+
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals("st")) {
+ readAttributionOp(parser, op, XmlUtils.readStringAttribute(parser, "id"));
+ } else {
+ Slog.w(TAG, "Unknown element under <op>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+
+ if (uidState.pkgOps == null) {
+ uidState.pkgOps = new ArrayMap<>();
+ }
+ Ops ops = uidState.pkgOps.get(pkgName);
+ if (ops == null) {
+ ops = new Ops(pkgName, uidState);
+ uidState.pkgOps.put(pkgName, ops);
+ }
+ ops.put(op.op, op);
+ }
+
+ @Override
+ public void writeState() {
+ synchronized (mFile) {
+ FileOutputStream stream;
+ try {
+ stream = mFile.startWrite();
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state: " + e);
+ return;
+ }
+
+ List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+ try {
+ TypedXmlSerializer out = Xml.resolveSerializer(stream);
+ out.startDocument(null, true);
+ out.startTag(null, "app-ops");
+ out.attributeInt(null, "v", CURRENT_VERSION);
+
+ SparseArray<SparseIntArray> uidStatesClone;
+ synchronized (this) {
+ uidStatesClone = new SparseArray<>(mUidStates.size());
+
+ final int uidStateCount = mUidStates.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ UidState uidState = mUidStates.valueAt(uidStateNum);
+ int uid = mUidStates.keyAt(uidStateNum);
+
+ SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ if (opModes != null && opModes.size() > 0) {
+ uidStatesClone.put(uid, opModes);
+ }
+ }
+ }
+
+ final int uidStateCount = uidStatesClone.size();
+ for (int uidStateNum = 0; uidStateNum < uidStateCount; uidStateNum++) {
+ SparseIntArray opModes = uidStatesClone.valueAt(uidStateNum);
+ if (opModes != null && opModes.size() > 0) {
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", uidStatesClone.keyAt(uidStateNum));
+ final int opCount = opModes.size();
+ for (int opCountNum = 0; opCountNum < opCount; opCountNum++) {
+ final int op = opModes.keyAt(opCountNum);
+ final int mode = opModes.valueAt(opCountNum);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op);
+ out.attributeInt(null, "m", mode);
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ }
+
+ if (allOps != null) {
+ String lastPkg = null;
+ for (int i = 0; i < allOps.size(); i++) {
+ AppOpsManager.PackageOps pkg = allOps.get(i);
+ if (!Objects.equals(pkg.getPackageName(), lastPkg)) {
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ lastPkg = pkg.getPackageName();
+ if (lastPkg != null) {
+ out.startTag(null, "pkg");
+ out.attribute(null, "n", lastPkg);
+ }
+ }
+ out.startTag(null, "uid");
+ out.attributeInt(null, "n", pkg.getUid());
+ List<AppOpsManager.OpEntry> ops = pkg.getOps();
+ for (int j = 0; j < ops.size(); j++) {
+ AppOpsManager.OpEntry op = ops.get(j);
+ out.startTag(null, "op");
+ out.attributeInt(null, "n", op.getOp());
+ if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+ out.attributeInt(null, "m", op.getMode());
+ }
+
+ for (String attributionTag : op.getAttributedOpEntries().keySet()) {
+ final AttributedOpEntry attribution =
+ op.getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = attribution.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = attribution.getLastAccessTime(uidState,
+ uidState, flags);
+ final long rejectTime = attribution.getLastRejectTime(uidState,
+ uidState, flags);
+ final long accessDuration = attribution.getLastDuration(
+ uidState, uidState, flags);
+ // Proxy information for rejections is not backed up
+ final OpEventProxyInfo proxy = attribution.getLastProxyInfo(
+ uidState, uidState, flags);
+
+ if (accessTime <= 0 && rejectTime <= 0 && accessDuration <= 0
+ && proxy == null) {
+ continue;
+ }
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ out.startTag(null, "st");
+ if (attributionTag != null) {
+ out.attribute(null, "id", attributionTag);
+ }
+ out.attributeLong(null, "n", key);
+ if (accessTime > 0) {
+ out.attributeLong(null, "t", accessTime);
+ }
+ if (rejectTime > 0) {
+ out.attributeLong(null, "r", rejectTime);
+ }
+ if (accessDuration > 0) {
+ out.attributeLong(null, "d", accessDuration);
+ }
+ if (proxyPkg != null) {
+ out.attribute(null, "pp", proxyPkg);
+ }
+ if (proxyAttributionTag != null) {
+ out.attribute(null, "pc", proxyAttributionTag);
+ }
+ if (proxyUid >= 0) {
+ out.attributeInt(null, "pu", proxyUid);
+ }
+ out.endTag(null, "st");
+ }
+ }
+
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "uid");
+ }
+ if (lastPkg != null) {
+ out.endTag(null, "pkg");
+ }
+ }
+
+ out.endTag(null, "app-ops");
+ out.endDocument();
+ mFile.finishWrite(stream);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write state, restoring backup.", e);
+ mFile.failWrite(stream);
+ }
+ }
+ mHistoricalRegistry.writeAndClearDiscreteHistory();
+ }
+
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("AppOps service (appops) dump options:");
+ pw.println(" -h");
+ pw.println(" Print this help text.");
+ pw.println(" --op [OP]");
+ pw.println(" Limit output to data associated with the given app op code.");
+ pw.println(" --mode [MODE]");
+ pw.println(" Limit output to data associated with the given app op mode.");
+ pw.println(" --package [PACKAGE]");
+ pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --attributionTag [attributionTag]");
+ pw.println(" Limit output to data associated with the given attribution tag.");
+ pw.println(" --include-discrete [n]");
+ pw.println(" Include discrete ops limited to n per dimension. Use zero for no limit.");
+ pw.println(" --watchers");
+ pw.println(" Only output the watcher sections.");
+ pw.println(" --history");
+ pw.println(" Only output history.");
+ pw.println(" --uid-state-changes");
+ pw.println(" Include logs about uid state changes.");
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
+ @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+ final int numAttributions = op.mAttributions.size();
+ for (int i = 0; i < numAttributions; i++) {
+ if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !Objects.equals(
+ op.mAttributions.keyAt(i), filterAttributionTag)) {
+ continue;
+ }
+
+ pw.print(prefix + op.mAttributions.keyAt(i) + "=[\n");
+ dumpStatesLocked(pw, nowElapsed, op, op.mAttributions.keyAt(i), now, sdf, date,
+ prefix + " ");
+ pw.print(prefix + "]\n");
+ }
+ }
+
+ private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
+ @Nullable String attributionTag, long now, @NonNull SimpleDateFormat sdf,
+ @NonNull Date date, @NonNull String prefix) {
+
+ final AttributedOpEntry entry = op.createSingleAttributionEntryLocked(
+ attributionTag).getAttributedOpEntries().get(attributionTag);
+
+ final ArraySet<Long> keys = entry.collectKeys();
+
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+
+ final int uidState = AppOpsManager.extractUidStateFromKey(key);
+ final int flags = AppOpsManager.extractFlagsFromKey(key);
+
+ final long accessTime = entry.getLastAccessTime(uidState, uidState, flags);
+ final long rejectTime = entry.getLastRejectTime(uidState, uidState, flags);
+ final long accessDuration = entry.getLastDuration(uidState, uidState, flags);
+ final OpEventProxyInfo proxy = entry.getLastProxyInfo(uidState, uidState, flags);
+
+ String proxyPkg = null;
+ String proxyAttributionTag = null;
+ int proxyUid = Process.INVALID_UID;
+ if (proxy != null) {
+ proxyPkg = proxy.getPackageName();
+ proxyAttributionTag = proxy.getAttributionTag();
+ proxyUid = proxy.getUid();
+ }
+
+ if (accessTime > 0) {
+ pw.print(prefix);
+ pw.print("Access: ");
+ pw.print(AppOpsManager.keyToString(key));
+ pw.print(" ");
+ date.setTime(accessTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(accessTime - now, pw);
+ pw.print(")");
+ if (accessDuration > 0) {
+ pw.print(" duration=");
+ TimeUtils.formatDuration(accessDuration, pw);
+ }
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+
+ if (rejectTime > 0) {
+ pw.print(prefix);
+ pw.print("Reject: ");
+ pw.print(AppOpsManager.keyToString(key));
+ date.setTime(rejectTime);
+ pw.print(sdf.format(date));
+ pw.print(" (");
+ TimeUtils.formatDuration(rejectTime - now, pw);
+ pw.print(")");
+ if (proxyUid >= 0) {
+ pw.print(" proxy[");
+ pw.print("uid=");
+ pw.print(proxyUid);
+ pw.print(", pkg=");
+ pw.print(proxyPkg);
+ pw.print(", attributionTag=");
+ pw.print(proxyAttributionTag);
+ pw.print("]");
+ }
+ pw.println();
+ }
+ }
+
+ final AttributedOp attributedOp = op.mAttributions.get(attributionTag);
+ if (attributedOp.isRunning()) {
+ long earliestElapsedTime = Long.MAX_VALUE;
+ long maxNumStarts = 0;
+ int numInProgressEvents = attributedOp.mInProgressEvents.size();
+ for (int i = 0; i < numInProgressEvents; i++) {
+ AttributedOp.InProgressStartOpEvent event =
+ attributedOp.mInProgressEvents.valueAt(i);
+
+ earliestElapsedTime = Math.min(earliestElapsedTime, event.getStartElapsedTime());
+ maxNumStarts = Math.max(maxNumStarts, event.mNumUnfinishedStarts);
+ }
+
+ pw.print(prefix + "Running start at: ");
+ TimeUtils.formatDuration(nowElapsed - earliestElapsedTime, pw);
+ pw.println();
+
+ if (maxNumStarts > 1) {
+ pw.print(prefix + "startNesting=");
+ pw.println(maxNumStarts);
+ }
+ }
+ }
+
+ @NeverCompile // Avoid size overhead of debugging code.
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
+
+ int dumpOp = OP_NONE;
+ String dumpPackage = null;
+ String dumpAttributionTag = null;
+ int dumpUid = Process.INVALID_UID;
+ int dumpMode = -1;
+ boolean dumpWatchers = false;
+ // TODO ntmyren: Remove the dumpHistory and dumpFilter
+ boolean dumpHistory = false;
+ boolean includeDiscreteOps = false;
+ boolean dumpUidStateChangeLogs = false;
+ int nDiscreteOps = 10;
+ @HistoricalOpsRequestFilter int dumpFilter = 0;
+ boolean dumpAll = false;
+
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if ("-h".equals(arg)) {
+ dumpHelp(pw);
+ return;
+ } else if ("-a".equals(arg)) {
+ // dump all data
+ dumpAll = true;
+ } else if ("--op".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --op option");
+ return;
+ }
+ dumpOp = AppOpsService.Shell.strOpToOp(args[i], pw);
+ dumpFilter |= FILTER_BY_OP_NAMES;
+ if (dumpOp < 0) {
+ return;
+ }
+ } else if ("--package".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --package option");
+ return;
+ }
+ dumpPackage = args[i];
+ dumpFilter |= FILTER_BY_PACKAGE_NAME;
+ try {
+ dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
+ PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
+ 0);
+ } catch (RemoteException e) {
+ }
+ if (dumpUid < 0) {
+ pw.println("Unknown package: " + dumpPackage);
+ return;
+ }
+ dumpUid = UserHandle.getAppId(dumpUid);
+ dumpFilter |= FILTER_BY_UID;
+ } else if ("--attributionTag".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --attributionTag option");
+ return;
+ }
+ dumpAttributionTag = args[i];
+ dumpFilter |= FILTER_BY_ATTRIBUTION_TAG;
+ } else if ("--mode".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --mode option");
+ return;
+ }
+ dumpMode = AppOpsService.Shell.strModeToMode(args[i], pw);
+ if (dumpMode < 0) {
+ return;
+ }
+ } else if ("--watchers".equals(arg)) {
+ dumpWatchers = true;
+ } else if ("--include-discrete".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --include-discrete option");
+ return;
+ }
+ try {
+ nDiscreteOps = Integer.valueOf(args[i]);
+ } catch (NumberFormatException e) {
+ pw.println("Wrong parameter: " + args[i]);
+ return;
+ }
+ includeDiscreteOps = true;
+ } else if ("--history".equals(arg)) {
+ dumpHistory = true;
+ } else if (arg.length() > 0 && arg.charAt(0) == '-') {
+ pw.println("Unknown option: " + arg);
+ return;
+ } else if ("--uid-state-changes".equals(arg)) {
+ dumpUidStateChangeLogs = true;
+ } else {
+ pw.println("Unknown command: " + arg);
+ return;
+ }
+ }
+ }
+
+ final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ final Date date = new Date();
+ synchronized (this) {
+ pw.println("Current AppOps Service state:");
+ if (!dumpHistory && !dumpWatchers) {
+ mConstants.dump(pw);
+ }
+ pw.println();
+ final long now = System.currentTimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ boolean needSep = false;
+ if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+ && !dumpHistory) {
+ pw.println(" Profile owners:");
+ for (int poi = 0; poi < mProfileOwners.size(); poi++) {
+ pw.print(" User #");
+ pw.print(mProfileOwners.keyAt(poi));
+ pw.print(": ");
+ UserHandle.formatUid(pw, mProfileOwners.valueAt(poi));
+ pw.println();
+ }
+ pw.println();
+ }
+
+ if (!dumpHistory) {
+ needSep |= mAppOpsServiceInterface.dumpListeners(dumpOp, dumpUid, dumpPackage, pw);
+ }
+
+ if (mModeWatchers.size() > 0 && dumpOp < 0 && !dumpHistory) {
+ boolean printedHeader = false;
+ for (int i = 0; i < mModeWatchers.size(); i++) {
+ final ModeCallback cb = mModeWatchers.valueAt(i);
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.getWatchingUid())) {
+ continue;
+ }
+ needSep = true;
+ if (!printedHeader) {
+ pw.println(" All op mode watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(mModeWatchers.keyAt(i))));
+ pw.print(": ");
+ pw.println(cb);
+ }
+ }
+ if (mActiveWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mActiveWatchers.size(); watcherNum++) {
+ final SparseArray<ActiveCallback> activeWatchers =
+ mActiveWatchers.valueAt(watcherNum);
+ if (activeWatchers.size() <= 0) {
+ continue;
+ }
+ final ActiveCallback cb = activeWatchers.valueAt(0);
+ if (dumpOp >= 0 && activeWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op active watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mActiveWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = activeWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(activeWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mStartedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+
+ final int watchersSize = mStartedWatchers.size();
+ for (int watcherNum = 0; watcherNum < watchersSize; watcherNum++) {
+ final SparseArray<StartedCallback> startedWatchers =
+ mStartedWatchers.valueAt(watcherNum);
+ if (startedWatchers.size() <= 0) {
+ continue;
+ }
+
+ final StartedCallback cb = startedWatchers.valueAt(0);
+ if (dumpOp >= 0 && startedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+
+ if (!printedHeader) {
+ pw.println(" All op started watchers:");
+ printedHeader = true;
+ }
+
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mStartedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+
+ pw.print(" [");
+ final int opCount = startedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+
+ pw.print(AppOpsManager.opToName(startedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (mNotedWatchers.size() > 0 && dumpMode < 0) {
+ needSep = true;
+ boolean printedHeader = false;
+ for (int watcherNum = 0; watcherNum < mNotedWatchers.size(); watcherNum++) {
+ final SparseArray<NotedCallback> notedWatchers =
+ mNotedWatchers.valueAt(watcherNum);
+ if (notedWatchers.size() <= 0) {
+ continue;
+ }
+ final NotedCallback cb = notedWatchers.valueAt(0);
+ if (dumpOp >= 0 && notedWatchers.indexOfKey(dumpOp) < 0) {
+ continue;
+ }
+ if (dumpPackage != null
+ && dumpUid != UserHandle.getAppId(cb.mWatchingUid)) {
+ continue;
+ }
+ if (!printedHeader) {
+ pw.println(" All op noted watchers:");
+ printedHeader = true;
+ }
+ pw.print(" ");
+ pw.print(Integer.toHexString(System.identityHashCode(
+ mNotedWatchers.keyAt(watcherNum))));
+ pw.println(" ->");
+ pw.print(" [");
+ final int opCount = notedWatchers.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ if (opNum > 0) {
+ pw.print(' ');
+ }
+ pw.print(AppOpsManager.opToName(notedWatchers.keyAt(opNum)));
+ if (opNum < opCount - 1) {
+ pw.print(',');
+ }
+ }
+ pw.println("]");
+ pw.print(" ");
+ pw.println(cb);
+ }
+ }
+ if (needSep) {
+ pw.println();
+ }
+ for (int i = 0; i < mUidStates.size(); i++) {
+ UidState uidState = mUidStates.valueAt(i);
+ final SparseIntArray opModes = uidState.getNonDefaultUidModes();
+ final ArrayMap<String, Ops> pkgOps = uidState.pkgOps;
+
+ if (dumpWatchers || dumpHistory) {
+ continue;
+ }
+ if (dumpOp >= 0 || dumpPackage != null || dumpMode >= 0) {
+ boolean hasOp = dumpOp < 0 || (opModes != null
+ && opModes.indexOfKey(dumpOp) >= 0);
+ boolean hasPackage = dumpPackage == null || dumpUid == mUidStates.keyAt(i);
+ boolean hasMode = dumpMode < 0;
+ if (!hasMode && opModes != null) {
+ for (int opi = 0; !hasMode && opi < opModes.size(); opi++) {
+ if (opModes.valueAt(opi) == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (pkgOps != null) {
+ for (int pkgi = 0;
+ (!hasOp || !hasPackage || !hasMode) && pkgi < pkgOps.size();
+ pkgi++) {
+ Ops ops = pkgOps.valueAt(pkgi);
+ if (!hasOp && ops != null && ops.indexOfKey(dumpOp) >= 0) {
+ hasOp = true;
+ }
+ if (!hasMode) {
+ for (int opi = 0; !hasMode && opi < ops.size(); opi++) {
+ if (ops.valueAt(opi).getMode() == dumpMode) {
+ hasMode = true;
+ }
+ }
+ }
+ if (!hasPackage && dumpPackage.equals(ops.packageName)) {
+ hasPackage = true;
+ }
+ }
+ }
+ if (uidState.foregroundOps != null && !hasOp) {
+ if (uidState.foregroundOps.indexOfKey(dumpOp) > 0) {
+ hasOp = true;
+ }
+ }
+ if (!hasOp || !hasPackage || !hasMode) {
+ continue;
+ }
+ }
+
+ pw.print(" Uid ");
+ UserHandle.formatUid(pw, uidState.uid);
+ pw.println(":");
+ uidState.dump(pw, nowElapsed);
+ if (uidState.foregroundOps != null && (dumpMode < 0
+ || dumpMode == AppOpsManager.MODE_FOREGROUND)) {
+ pw.println(" foregroundOps:");
+ for (int j = 0; j < uidState.foregroundOps.size(); j++) {
+ if (dumpOp >= 0 && dumpOp != uidState.foregroundOps.keyAt(j)) {
+ continue;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(uidState.foregroundOps.keyAt(j)));
+ pw.print(": ");
+ pw.println(uidState.foregroundOps.valueAt(j) ? "WATCHER" : "SILENT");
+ }
+ pw.print(" hasForegroundWatchers=");
+ pw.println(uidState.hasForegroundWatchers);
+ }
+ needSep = true;
+
+ if (opModes != null) {
+ final int opModeCount = opModes.size();
+ for (int j = 0; j < opModeCount; j++) {
+ final int code = opModes.keyAt(j);
+ final int mode = opModes.valueAt(j);
+ if (dumpOp >= 0 && dumpOp != code) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != mode) {
+ continue;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(code));
+ pw.print(": mode=");
+ pw.println(AppOpsManager.modeToName(mode));
+ }
+ }
+
+ if (pkgOps == null) {
+ continue;
+ }
+
+ for (int pkgi = 0; pkgi < pkgOps.size(); pkgi++) {
+ final Ops ops = pkgOps.valueAt(pkgi);
+ if (dumpPackage != null && !dumpPackage.equals(ops.packageName)) {
+ continue;
+ }
+ boolean printedPackage = false;
+ for (int j = 0; j < ops.size(); j++) {
+ final Op op = ops.valueAt(j);
+ final int opCode = op.op;
+ if (dumpOp >= 0 && dumpOp != opCode) {
+ continue;
+ }
+ if (dumpMode >= 0 && dumpMode != op.getMode()) {
+ continue;
+ }
+ if (!printedPackage) {
+ pw.print(" Package ");
+ pw.print(ops.packageName);
+ pw.println(":");
+ printedPackage = true;
+ }
+ pw.print(" ");
+ pw.print(AppOpsManager.opToName(opCode));
+ pw.print(" (");
+ pw.print(AppOpsManager.modeToName(op.getMode()));
+ final int switchOp = AppOpsManager.opToSwitch(opCode);
+ if (switchOp != opCode) {
+ pw.print(" / switch ");
+ pw.print(AppOpsManager.opToName(switchOp));
+ final Op switchObj = ops.get(switchOp);
+ int mode = switchObj == null
+ ? AppOpsManager.opToDefaultMode(switchOp) : switchObj.getMode();
+ pw.print("=");
+ pw.print(AppOpsManager.modeToName(mode));
+ }
+ pw.println("): ");
+ dumpStatesLocked(pw, dumpAttributionTag, dumpFilter, nowElapsed, op, now,
+ sdf, date, " ");
+ }
+ }
+ }
+ if (needSep) {
+ pw.println();
+ }
+
+ boolean showUserRestrictions = !(dumpMode < 0 && !dumpWatchers && !dumpHistory);
+ mAppOpsRestrictions.dumpRestrictions(pw, dumpOp, dumpPackage, showUserRestrictions);
+
+ if (dumpAll || dumpUidStateChangeLogs) {
+ pw.println();
+ pw.println("Uid State Changes Event Log:");
+ getUidStateTracker().dumpEvents(pw);
+ }
+ }
+
+ // Must not hold the appops lock
+ if (dumpHistory && !dumpWatchers) {
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpAttributionTag, dumpOp,
+ dumpFilter);
+ }
+ if (includeDiscreteOps) {
+ pw.println("Discrete accesses: ");
+ mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
+ dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
+ }
+ }
+
+ @Override
+ public void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle) {
+ checkSystemUid("setUserRestrictions");
+ Objects.requireNonNull(restrictions);
+ Objects.requireNonNull(token);
+ for (int i = 0; i < AppOpsManager._NUM_OP; i++) {
+ String restriction = AppOpsManager.opToRestriction(i);
+ if (restriction != null) {
+ setUserRestrictionNoCheck(i, restrictions.getBoolean(restriction, false), token,
+ userHandle, null);
+ }
+ }
+ }
+
+ @Override
+ public void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ PackageTagsList excludedPackageTags) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ mContext.enforcePermission(Manifest.permission.MANAGE_APP_OPS_RESTRICTIONS,
+ Binder.getCallingPid(), Binder.getCallingUid(), null);
+ }
+ if (userHandle != UserHandle.getCallingUserId()) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED
+ && mContext.checkCallingOrSelfPermission(Manifest.permission
+ .INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Need INTERACT_ACROSS_USERS_FULL or"
+ + " INTERACT_ACROSS_USERS to interact cross user ");
+ }
+ }
+ verifyIncomingOp(code);
+ Objects.requireNonNull(token);
+ setUserRestrictionNoCheck(code, restricted, token, userHandle, excludedPackageTags);
+ }
+
+ private void setUserRestrictionNoCheck(int code, boolean restricted, IBinder token,
+ int userHandle, PackageTagsList excludedPackageTags) {
+ synchronized (AppOpsServiceImpl.this) {
+ ClientUserRestrictionState restrictionState = mOpUserRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientUserRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpUserRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted, excludedPackageTags,
+ userHandle)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+ restricted, userHandle));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpUserRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ @Override
+ public void setGlobalRestriction(int code, boolean restricted, IBinder token) {
+ if (Binder.getCallingPid() != Process.myPid()) {
+ throw new SecurityException("Only the system can set global restrictions");
+ }
+
+ synchronized (this) {
+ ClientGlobalRestrictionState restrictionState = mOpGlobalRestrictions.get(token);
+
+ if (restrictionState == null) {
+ try {
+ restrictionState = new ClientGlobalRestrictionState(token);
+ } catch (RemoteException e) {
+ return;
+ }
+ mOpGlobalRestrictions.put(token, restrictionState);
+ }
+
+ if (restrictionState.setRestriction(code, restricted)) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::notifyWatchersOfChange, this, code, UID_ANY));
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ AppOpsServiceImpl::updateStartedOpModeForUser, this, code,
+ restricted, UserHandle.USER_ALL));
+ }
+
+ if (restrictionState.isDefault()) {
+ mOpGlobalRestrictions.remove(token);
+ restrictionState.destroy();
+ }
+ }
+ }
+
+ @Override
+ public int getOpRestrictionCount(int code, UserHandle user, String pkg,
+ String attributionTag) {
+ int number = 0;
+ synchronized (this) {
+ int numRestrictions = mOpUserRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpUserRestrictions.valueAt(i)
+ .hasRestriction(code, pkg, attributionTag, user.getIdentifier(),
+ false)) {
+ number++;
+ }
+ }
+
+ numRestrictions = mOpGlobalRestrictions.size();
+ for (int i = 0; i < numRestrictions; i++) {
+ if (mOpGlobalRestrictions.valueAt(i).hasRestriction(code)) {
+ number++;
+ }
+ }
+ }
+
+ return number;
+ }
+
+ private void updateStartedOpModeForUser(int code, boolean restricted, int userId) {
+ synchronized (AppOpsServiceImpl.this) {
+ int numUids = mUidStates.size();
+ for (int uidNum = 0; uidNum < numUids; uidNum++) {
+ int uid = mUidStates.keyAt(uidNum);
+ if (userId != UserHandle.USER_ALL && UserHandle.getUserId(uid) != userId) {
+ continue;
+ }
+ updateStartedOpModeForUidLocked(code, restricted, uid);
+ }
+ }
+ }
+
+ private void updateStartedOpModeForUidLocked(int code, boolean restricted, int uid) {
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+
+ int numPkgOps = uidState.pkgOps.size();
+ for (int pkgNum = 0; pkgNum < numPkgOps; pkgNum++) {
+ Ops ops = uidState.pkgOps.valueAt(pkgNum);
+ Op op = ops != null ? ops.get(code) : null;
+ if (op == null || (op.getMode() != MODE_ALLOWED && op.getMode() != MODE_FOREGROUND)) {
+ continue;
+ }
+ int numAttrTags = op.mAttributions.size();
+ for (int attrNum = 0; attrNum < numAttrTags; attrNum++) {
+ AttributedOp attrOp = op.mAttributions.valueAt(attrNum);
+ if (restricted && attrOp.isRunning()) {
+ attrOp.pause();
+ } else if (attrOp.isPaused()) {
+ attrOp.resume();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void notifyWatchersOfChange(int code, int uid) {
+ final ArraySet<OnOpModeChangedListener> modeChangedListenerSet;
+ synchronized (this) {
+ modeChangedListenerSet = mAppOpsServiceInterface.getOpModeChangedListeners(code);
+ if (modeChangedListenerSet == null) {
+ return;
+ }
+ }
+
+ notifyOpChanged(modeChangedListenerSet, code, uid, null);
+ }
+
+ @Override
+ public void removeUser(int userHandle) throws RemoteException {
+ checkSystemUid("removeUser");
+ synchronized (AppOpsServiceImpl.this) {
+ final int tokenCount = mOpUserRestrictions.size();
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+ opRestrictions.removeUser(userHandle);
+ }
+ removeUidsForUserLocked(userHandle);
+ }
+ }
+
+ @Override
+ public boolean isOperationActive(int code, int uid, String packageName) {
+ if (Binder.getCallingUid() != uid) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.WATCH_APPOPS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return false;
+ }
+ }
+ verifyIncomingOp(code);
+ if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ return false;
+ }
+
+ final String resolvedPackageName = AppOpsManager.resolvePackageName(uid, packageName);
+ if (resolvedPackageName == null) {
+ return false;
+ }
+ // TODO moltmann: Allow to check for attribution op activeness
+ synchronized (AppOpsServiceImpl.this) {
+ Ops pkgOps = getOpsLocked(uid, resolvedPackageName, null, false, null, false);
+ if (pkgOps == null) {
+ return false;
+ }
+
+ Op op = pkgOps.get(code);
+ if (op == null) {
+ return false;
+ }
+
+ return op.isRunning();
+ }
+ }
+
+ @Override
+ public boolean isProxying(int op, @NonNull String proxyPackageName,
+ @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName) {
+ Objects.requireNonNull(proxyPackageName);
+ Objects.requireNonNull(proxiedPackageName);
+ final long callingUid = Binder.getCallingUid();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final List<AppOpsManager.PackageOps> packageOps = getOpsForPackage(proxiedUid,
+ proxiedPackageName, new int[]{op});
+ if (packageOps == null || packageOps.isEmpty()) {
+ return false;
+ }
+ final List<OpEntry> opEntries = packageOps.get(0).getOps();
+ if (opEntries.isEmpty()) {
+ return false;
+ }
+ final OpEntry opEntry = opEntries.get(0);
+ if (!opEntry.isRunning()) {
+ return false;
+ }
+ final OpEventProxyInfo proxyInfo = opEntry.getLastProxyInfo(
+ OP_FLAG_TRUSTED_PROXIED | AppOpsManager.OP_FLAG_UNTRUSTED_PROXIED);
+ return proxyInfo != null && callingUid == proxyInfo.getUid()
+ && proxyPackageName.equals(proxyInfo.getPackageName())
+ && Objects.equals(proxyAttributionTag, proxyInfo.getAttributionTag());
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void resetPackageOpsNoHistory(@NonNull String packageName) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetPackageOpsNoHistory");
+ synchronized (AppOpsServiceImpl.this) {
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, 0,
+ UserHandle.getCallingUserId());
+ if (uid == Process.INVALID_UID) {
+ return;
+ }
+ UidState uidState = mUidStates.get(uid);
+ if (uidState == null || uidState.pkgOps == null) {
+ return;
+ }
+ Ops removedOps = uidState.pkgOps.remove(packageName);
+ mAppOpsServiceInterface.removePackage(packageName, UserHandle.getUserId(uid));
+ if (removedOps != null) {
+ scheduleFastWriteLocked();
+ }
+ }
+ }
+
+ @Override
+ public void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "setHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+ }
+
+ @Override
+ public void offsetHistory(long offsetMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "offsetHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.offsetHistory(offsetMillis);
+ mHistoricalRegistry.offsetDiscreteHistory(offsetMillis);
+ }
+
+ @Override
+ public void addHistoricalOps(HistoricalOps ops) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "addHistoricalOps");
+ // Must not hold the appops lock
+ mHistoricalRegistry.addHistoricalOps(ops);
+ }
+
+ @Override
+ public void resetHistoryParameters() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "resetHistoryParameters");
+ // Must not hold the appops lock
+ mHistoricalRegistry.resetHistoryParameters();
+ }
+
+ @Override
+ public void clearHistory() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "clearHistory");
+ // Must not hold the appops lock
+ mHistoricalRegistry.clearAllHistory();
+ }
+
+ @Override
+ public void rebootHistory(long offlineDurationMillis) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APPOPS,
+ "rebootHistory");
+
+ Preconditions.checkArgument(offlineDurationMillis >= 0);
+
+ // Must not hold the appops lock
+ mHistoricalRegistry.shutdown();
+
+ if (offlineDurationMillis > 0) {
+ SystemClock.sleep(offlineDurationMillis);
+ }
+
+ mHistoricalRegistry = new HistoricalRegistry(mHistoricalRegistry);
+ mHistoricalRegistry.systemReady(mContext.getContentResolver());
+ mHistoricalRegistry.persistPendingHistory();
+ }
+
+ @GuardedBy("this")
+ private void removeUidsForUserLocked(int userHandle) {
+ for (int i = mUidStates.size() - 1; i >= 0; --i) {
+ final int uid = mUidStates.keyAt(i);
+ if (UserHandle.getUserId(uid) == userHandle) {
+ mUidStates.valueAt(i).clear();
+ mUidStates.removeAt(i);
+ }
+ }
+ }
+
+ private void checkSystemUid(String function) {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(function + " must by called by the system");
+ }
+ }
+
+ private static int resolveUid(String packageName) {
+ if (packageName == null) {
+ return Process.INVALID_UID;
+ }
+ switch (packageName) {
+ case "root":
+ return Process.ROOT_UID;
+ case "shell":
+ case "dumpstate":
+ return Process.SHELL_UID;
+ case "media":
+ return Process.MEDIA_UID;
+ case "audioserver":
+ return Process.AUDIOSERVER_UID;
+ case "cameraserver":
+ return Process.CAMERASERVER_UID;
+ }
+ return Process.INVALID_UID;
+ }
+
+ private static String[] getPackagesForUid(int uid) {
+ String[] packageNames = null;
+
+ // Very early during boot the package manager is not yet or not yet fully started. At this
+ // time there are no packages yet.
+ if (AppGlobals.getPackageManager() != null) {
+ try {
+ packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+ } catch (RemoteException e) {
+ /* ignore - local call */
+ }
+ }
+ if (packageNames == null) {
+ return EmptyArray.STRING;
+ }
+ return packageNames;
+ }
+
+ private final class ClientUserRestrictionState implements DeathRecipient {
+ private final IBinder mToken;
+
+ ClientUserRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.mToken = token;
+ }
+
+ public boolean setRestriction(int code, boolean restricted,
+ PackageTagsList excludedPackageTags, int userId) {
+ return mAppOpsRestrictions.setUserRestriction(mToken, userId, code,
+ restricted, excludedPackageTags);
+ }
+
+ public boolean hasRestriction(int code, String packageName, String attributionTag,
+ int userId, boolean isCheckOp) {
+ return mAppOpsRestrictions.getUserRestriction(mToken, userId, code, packageName,
+ attributionTag, isCheckOp);
+ }
+
+ public void removeUser(int userId) {
+ mAppOpsRestrictions.clearUserRestrictions(mToken, userId);
+ }
+
+ public boolean isDefault() {
+ return !mAppOpsRestrictions.hasUserRestrictions(mToken);
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (AppOpsServiceImpl.this) {
+ mAppOpsRestrictions.clearUserRestrictions(mToken);
+ mOpUserRestrictions.remove(mToken);
+ destroy();
+ }
+ }
+
+ public void destroy() {
+ mToken.unlinkToDeath(this, 0);
+ }
+ }
+
+ private final class ClientGlobalRestrictionState implements DeathRecipient {
+ final IBinder mToken;
+
+ ClientGlobalRestrictionState(IBinder token)
+ throws RemoteException {
+ token.linkToDeath(this, 0);
+ this.mToken = token;
+ }
+
+ boolean setRestriction(int code, boolean restricted) {
+ return mAppOpsRestrictions.setGlobalRestriction(mToken, code, restricted);
+ }
+
+ boolean hasRestriction(int code) {
+ return mAppOpsRestrictions.getGlobalRestriction(mToken, code);
+ }
+
+ boolean isDefault() {
+ return !mAppOpsRestrictions.hasGlobalRestrictions(mToken);
+ }
+
+ @Override
+ public void binderDied() {
+ mAppOpsRestrictions.clearGlobalRestrictions(mToken);
+ mOpGlobalRestrictions.remove(mToken);
+ destroy();
+ }
+
+ void destroy() {
+ mToken.unlinkToDeath(this, 0);
+ }
+ }
+
+ @Override
+ public void setDeviceAndProfileOwners(SparseIntArray owners) {
+ synchronized (this) {
+ mProfileOwners = owners;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
index 18f659e..8420fcb 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceInterface.java
@@ -13,197 +13,482 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package com.android.server.appop;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager.Mode;
-import android.util.ArraySet;
-import android.util.SparseBooleanArray;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.AttributionSource;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PackageTagsList;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.SparseArray;
import android.util.SparseIntArray;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsStartedCallback;
+
+import dalvik.annotation.optimization.NeverCompile;
+
+import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.List;
/**
- * Interface for accessing and modifying modes for app-ops i.e. package and uid modes.
- * This interface also includes functions for added and removing op mode watchers.
- * In the future this interface will also include op restrictions.
+ *
*/
-public interface AppOpsServiceInterface {
- /**
- * Returns a copy of non-default app-ops with op as keys and their modes as values for a uid.
- * Returns an empty SparseIntArray if nothing is set.
- * @param uid for which we need the app-ops and their modes.
- */
- SparseIntArray getNonDefaultUidModes(int uid);
+public interface AppOpsServiceInterface extends PersistenceScheduler {
/**
- * Returns the app-op mode for a particular app-op of a uid.
- * Returns default op mode if the op mode for particular uid and op is not set.
- * @param uid user id for which we need the mode.
- * @param op app-op for which we need the mode.
- * @return mode of the app-op.
- */
- int getUidMode(int uid, int op);
-
- /**
- * Set the app-op mode for a particular uid and op.
- * The mode is not set if the mode is the same as the default mode for the op.
- * @param uid user id for which we want to set the mode.
- * @param op app-op for which we want to set the mode.
- * @param mode mode for the app-op.
- * @return true if op mode is changed.
- */
- boolean setUidMode(int uid, int op, @Mode int mode);
-
- /**
- * Gets the app-op mode for a particular package.
- * Returns default op mode if the op mode for the particular package is not set.
- * @param packageName package name for which we need the op mode.
- * @param op app-op for which we need the mode.
- * @param userId user id associated with the package.
- * @return the mode of the app-op.
- */
- int getPackageMode(@NonNull String packageName, int op, @UserIdInt int userId);
-
- /**
- * Sets the app-op mode for a particular package.
- * @param packageName package name for which we need to set the op mode.
- * @param op app-op for which we need to set the mode.
- * @param mode the mode of the app-op.
- * @param userId user id associated with the package.
*
*/
- void setPackageMode(@NonNull String packageName, int op, @Mode int mode, @UserIdInt int userId);
+ void systemReady();
/**
- * Stop tracking any app-op modes for a package.
- * @param packageName Name of the package for which we want to remove all mode tracking.
- * @param userId user id associated with the package.
+ *
*/
- boolean removePackage(@NonNull String packageName, @UserIdInt int userId);
+ void shutdown();
/**
- * Stop tracking any app-op modes for this uid.
- * @param uid user id for which we want to remove all tracking.
+ *
+ * @param uid
+ * @param packageName
*/
- void removeUid(int uid);
+ void verifyPackage(int uid, String packageName);
/**
- * Returns true if all uid modes for this uid are
- * in default state.
- * @param uid user id
+ *
+ * @param op
+ * @param packageName
+ * @param flags
+ * @param callback
*/
- boolean areUidModesDefault(int uid);
+ void startWatchingModeWithFlags(int op, String packageName, int flags,
+ IAppOpsCallback callback);
/**
- * Returns true if all package modes for this package name are
- * in default state.
- * @param packageName package name.
- * @param userId user id associated with the package.
+ *
+ * @param callback
*/
- boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+ void stopWatchingMode(IAppOpsCallback callback);
/**
- * Stop tracking app-op modes for all uid and packages.
+ *
+ * @param ops
+ * @param callback
*/
- void clearAllModes();
+ void startWatchingActive(int[] ops, IAppOpsActiveCallback callback);
/**
- * Registers changedListener to listen to op's mode change.
- * @param changedListener the listener that must be trigger on the op's mode change.
- * @param op op representing the app-op whose mode change needs to be listened to.
+ *
+ * @param callback
*/
- void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener, int op);
+ void stopWatchingActive(IAppOpsActiveCallback callback);
/**
- * Registers changedListener to listen to package's app-op's mode change.
- * @param changedListener the listener that must be trigger on the mode change.
- * @param packageName of the package whose app-op's mode change needs to be listened to.
+ *
+ * @param ops
+ * @param callback
*/
- void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
- @NonNull String packageName);
+ void startWatchingStarted(int[] ops, @NonNull IAppOpsStartedCallback callback);
/**
- * Stop the changedListener from triggering on any mode change.
- * @param changedListener the listener that needs to be removed.
+ *
+ * @param callback
*/
- void removeListener(@NonNull OnOpModeChangedListener changedListener);
+ void stopWatchingStarted(IAppOpsStartedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Returns a set of OnOpModeChangedListener that are listening for op's mode changes.
- * @param op app-op whose mode change is being listened to.
+ *
+ * @param ops
+ * @param callback
*/
- ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op);
+ void startWatchingNoted(@NonNull int[] ops, @NonNull IAppOpsNotedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Returns a set of OnOpModeChangedListener that are listening for package's op's mode changes.
- * @param packageName of package whose app-op's mode change is being listened to.
+ *
+ * @param callback
*/
- ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(@NonNull String packageName);
+ void stopWatchingNoted(IAppOpsNotedCallback callback);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed by triggering the change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op (or, if UID_ANY, notifies all users)
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param startIfModeDefault
+ * @param message
+ * @param attributionFlags
+ * @param attributionChainId
+ * @return
*/
- void notifyWatchersOfChange(int op, int uid);
+ int startOperation(@NonNull IBinder clientId, int code, int uid,
+ @Nullable String packageName, @Nullable String attributionTag,
+ boolean startIfModeDefault, @NonNull String message,
+ @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId);
+
+
+ int startOperationUnchecked(IBinder clientId, int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags,
+ boolean startIfModeDefault, @AppOpsManager.AttributionFlags int attributionFlags,
+ int attributionChainId, boolean dryRun);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed by triggering the change listener.
- * @param changedListener the change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op
- * @param packageName package name that is associated with the app-op
+ *
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
*/
- void notifyOpChanged(@NonNull OnOpModeChangedListener changedListener, int op, int uid,
- @Nullable String packageName);
+ void finishOperation(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag);
/**
- * Temporary API which will be removed once we can safely untangle the methods that use this.
- * Notify that the app-op's mode is changed to all packages associated with the uid by
- * triggering the appropriate change listener.
- * @param op App-op whose mode has changed
- * @param uid user id associated with the app-op
- * @param onlyForeground true if only watchers that
- * @param callbackToIgnore callback that should be ignored.
+ *
+ * @param clientId
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
*/
- void notifyOpChangedForAllPkgsInUid(int op, int uid, boolean onlyForeground,
- @Nullable OnOpModeChangedListener callbackToIgnore);
+ void finishOperationUnchecked(IBinder clientId, int code, int uid, String packageName,
+ String attributionTag);
/**
- * TODO: Move hasForegroundWatchers and foregroundOps into this.
- * Go over the list of app-ops for the uid and mark app-ops with MODE_FOREGROUND in
- * foregroundOps.
- * @param uid for which the app-op's mode needs to be marked.
- * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
- * @return foregroundOps.
+ *
+ * @param uidPackageNames
+ * @param visible
*/
- SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+ void updateAppWidgetVisibility(SparseArray<String> uidPackageNames, boolean visible);
/**
- * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
- * foregroundOps.
- * @param packageName for which the app-op's mode needs to be marked.
- * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
- * @param userId user id associated with the package.
- * @return foregroundOps.
+ *
*/
- SparseBooleanArray evalForegroundPackageOps(String packageName,
- SparseBooleanArray foregroundOps, @UserIdInt int userId);
+ void readState();
/**
- * Dump op mode and package mode listeners and their details.
- * @param dumpOp if -1 then op mode listeners for all app-ops are dumped. If it's set to an
- * app-op, only the watchers for that app-op are dumped.
- * @param dumpUid uid for which we want to dump op mode watchers.
- * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
- * @param printWriter writer to dump to.
+ *
*/
- boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+ void writeState();
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ */
+ void packageRemoved(int uid, String packageName);
+
+ /**
+ *
+ * @param uid
+ */
+ void uidRemoved(int uid);
+
+ /**
+ *
+ * @param uid
+ * @param procState
+ * @param capability
+ */
+ void updateUidProcState(int uid, int procState,
+ @ActivityManager.ProcessCapability int capability);
+
+ /**
+ *
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+ int[] ops);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param opNames
+ * @param dataType
+ * @param filter
+ * @param beginTimeMillis
+ * @param endTimeMillis
+ * @param flags
+ * @param callback
+ */
+ void getHistoricalOps(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param opNames
+ * @param dataType
+ * @param filter
+ * @param beginTimeMillis
+ * @param endTimeMillis
+ * @param flags
+ * @param callback
+ */
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
+ List<String> opNames, int dataType, int filter, long beginTimeMillis,
+ long endTimeMillis, int flags, RemoteCallback callback);
+
+ /**
+ *
+ */
+ void reloadNonHistoricalState();
+
+ /**
+ *
+ * @param uid
+ * @param ops
+ * @return
+ */
+ List<AppOpsManager.PackageOps> getUidOps(int uid, int[] ops);
+
+ /**
+ *
+ * @param owners
+ */
+ void setDeviceAndProfileOwners(SparseIntArray owners);
+
+ // used in audio restriction calls, might just copy the logic to avoid having this call.
+ /**
+ *
+ * @param callingPid
+ * @param callingUid
+ * @param targetUid
+ */
+ void enforceManageAppOpsModes(int callingPid, int callingUid, int targetUid);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param mode
+ * @param permissionPolicyCallback
+ */
+ void setUidMode(int code, int uid, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param mode
+ * @param permissionPolicyCallback
+ */
+ void setMode(int code, int uid, @NonNull String packageName, int mode,
+ @Nullable IAppOpsCallback permissionPolicyCallback);
+
+ /**
+ *
+ * @param reqUserId
+ * @param reqPackageName
+ */
+ void resetAllModes(int reqUserId, String reqPackageName);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param raw
+ * @return
+ */
+ int checkOperation(int code, int uid, String packageName,
+ @Nullable String attributionTag, boolean raw);
+
+ /**
+ *
+ * @param uid
+ * @param packageName
+ * @return
+ */
+ int checkPackage(int uid, String packageName);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param message
+ * @return
+ */
+ int noteOperation(int code, int uid, @Nullable String packageName,
+ @Nullable String attributionTag, @Nullable String message);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @param attributionTag
+ * @param proxyUid
+ * @param proxyPackageName
+ * @param proxyAttributionTag
+ * @param flags
+ * @return
+ */
+ @AppOpsManager.Mode
+ int noteOperationUnchecked(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int proxyUid, String proxyPackageName,
+ @Nullable String proxyAttributionTag, @AppOpsManager.OpFlags int flags);
+
+ boolean isAttributionTagValid(int uid, @NonNull String packageName,
+ @Nullable String attributionTag, @Nullable String proxyPackageName);
+
+ /**
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ @NeverCompile
+ // Avoid size overhead of debugging code.
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+ /**
+ *
+ * @param restrictions
+ * @param token
+ * @param userHandle
+ */
+ void setUserRestrictions(Bundle restrictions, IBinder token, int userHandle);
+
+ /**
+ *
+ * @param code
+ * @param restricted
+ * @param token
+ * @param userHandle
+ * @param excludedPackageTags
+ */
+ void setUserRestriction(int code, boolean restricted, IBinder token, int userHandle,
+ PackageTagsList excludedPackageTags);
+
+ /**
+ *
+ * @param code
+ * @param restricted
+ * @param token
+ */
+ void setGlobalRestriction(int code, boolean restricted, IBinder token);
+
+ /**
+ *
+ * @param code
+ * @param user
+ * @param pkg
+ * @param attributionTag
+ * @return
+ */
+ int getOpRestrictionCount(int code, UserHandle user, String pkg,
+ String attributionTag);
+
+ /**
+ *
+ * @param code
+ * @param uid
+ */
+ // added to interface for audio restriction stuff
+ void notifyWatchersOfChange(int code, int uid);
+
+ /**
+ *
+ * @param userHandle
+ * @throws RemoteException
+ */
+ void removeUser(int userHandle) throws RemoteException;
+
+ /**
+ *
+ * @param code
+ * @param uid
+ * @param packageName
+ * @return
+ */
+ boolean isOperationActive(int code, int uid, String packageName);
+
+ /**
+ *
+ * @param op
+ * @param proxyPackageName
+ * @param proxyAttributionTag
+ * @param proxiedUid
+ * @param proxiedPackageName
+ * @return
+ */
+ // TODO this one might not need to be in the interface
+ boolean isProxying(int op, @NonNull String proxyPackageName,
+ @NonNull String proxyAttributionTag, int proxiedUid,
+ @NonNull String proxiedPackageName);
+
+ /**
+ *
+ * @param packageName
+ */
+ void resetPackageOpsNoHistory(@NonNull String packageName);
+
+ /**
+ *
+ * @param mode
+ * @param baseSnapshotInterval
+ * @param compressionStep
+ */
+ void setHistoryParameters(@AppOpsManager.HistoricalMode int mode,
+ long baseSnapshotInterval, int compressionStep);
+
+ /**
+ *
+ * @param offsetMillis
+ */
+ void offsetHistory(long offsetMillis);
+
+ /**
+ *
+ * @param ops
+ */
+ void addHistoricalOps(AppOpsManager.HistoricalOps ops);
+
+ /**
+ *
+ */
+ void resetHistoryParameters();
+
+ /**
+ *
+ */
+ void clearHistory();
+
+ /**
+ *
+ * @param offlineDurationMillis
+ */
+ void rebootHistory(long offlineDurationMillis);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index 5114bd5..c1434e4 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -59,7 +59,7 @@
private final DelayableExecutor mExecutor;
private final Clock mClock;
private ActivityManagerInternal mActivityManagerInternal;
- private AppOpsService.Constants mConstants;
+ private AppOpsServiceImpl.Constants mConstants;
private SparseIntArray mUidStates = new SparseIntArray();
private SparseIntArray mPendingUidStates = new SparseIntArray();
@@ -85,7 +85,7 @@
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
Handler handler, Executor lockingExecutor, Clock clock,
- AppOpsService.Constants constants) {
+ AppOpsServiceImpl.Constants constants) {
this(activityManagerInternal, new DelayableExecutor() {
@Override
@@ -102,7 +102,7 @@
@VisibleForTesting
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
- DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+ DelayableExecutor executor, Clock clock, AppOpsServiceImpl.Constants constants,
Thread executorThread) {
mActivityManagerInternal = activityManagerInternal;
mExecutor = executor;
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index dcc36bc..7970269 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -40,9 +40,9 @@
import java.util.NoSuchElementException;
final class AttributedOp {
- private final @NonNull AppOpsService mAppOpsService;
+ private final @NonNull AppOpsServiceImpl mAppOpsService;
public final @Nullable String tag;
- public final @NonNull AppOpsService.Op parent;
+ public final @NonNull AppOpsServiceImpl.Op parent;
/**
* Last successful accesses (noteOp + finished startOp) for each uidState/opFlag combination
@@ -80,8 +80,8 @@
// @GuardedBy("mAppOpsService")
@Nullable ArrayMap<IBinder, InProgressStartOpEvent> mPausedInProgressEvents;
- AttributedOp(@NonNull AppOpsService appOpsService, @Nullable String tag,
- @NonNull AppOpsService.Op parent) {
+ AttributedOp(@NonNull AppOpsServiceImpl appOpsService, @Nullable String tag,
+ @NonNull AppOpsServiceImpl.Op parent) {
mAppOpsService = appOpsService;
this.tag = tag;
this.parent = parent;
@@ -131,8 +131,8 @@
AppOpsManager.OpEventProxyInfo proxyInfo = null;
if (proxyUid != Process.INVALID_UID) {
- proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid, proxyPackageName,
- proxyAttributionTag);
+ proxyInfo = mAppOpsService.mOpEventProxyInfoPool.acquire(proxyUid,
+ proxyPackageName, proxyAttributionTag);
}
AppOpsManager.NoteOpEvent existingEvent = mAccessEvents.get(key);
@@ -238,7 +238,7 @@
if (event == null) {
event = mAppOpsService.mInProgressStartOpEventPool.acquire(startTime,
SystemClock.elapsedRealtime(), clientId, tag,
- PooledLambda.obtainRunnable(AppOpsService::onClientDeath, this, clientId),
+ PooledLambda.obtainRunnable(AppOpsServiceImpl::onClientDeath, this, clientId),
proxyUid, proxyPackageName, proxyAttributionTag, uidState, flags,
attributionFlags, attributionChainId);
events.put(clientId, event);
@@ -251,9 +251,9 @@
event.mNumUnfinishedStarts++;
if (isStarted) {
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
- parent.packageName, tag, uidState, flags, startTime, attributionFlags,
- attributionChainId);
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+ parent.uid, parent.packageName, tag, uidState, flags, startTime,
+ attributionFlags, attributionChainId);
}
}
@@ -309,8 +309,8 @@
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
finishedEvent);
- mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
- parent.packageName, tag, event.getUidState(),
+ mAppOpsService.mHistoricalRegistry.increaseOpAccessDuration(parent.op,
+ parent.uid, parent.packageName, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
event.getAttributionFlags(), event.getAttributionChainId());
@@ -334,13 +334,13 @@
@SuppressWarnings("GuardedBy") // Lock is held on mAppOpsService
private void finishPossiblyPaused(@NonNull IBinder clientId, boolean isPausing) {
if (!isPaused()) {
- Slog.wtf(AppOpsService.TAG, "No ops running or paused");
+ Slog.wtf(AppOpsServiceImpl.TAG, "No ops running or paused");
return;
}
int indexOfToken = mPausedInProgressEvents.indexOfKey(clientId);
if (indexOfToken < 0) {
- Slog.wtf(AppOpsService.TAG, "No op running or paused for the client");
+ Slog.wtf(AppOpsServiceImpl.TAG, "No op running or paused for the client");
return;
} else if (isPausing) {
// already paused
@@ -416,9 +416,9 @@
mInProgressEvents.put(event.getClientId(), event);
event.setStartElapsedTime(SystemClock.elapsedRealtime());
event.setStartTime(startTime);
- mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
- parent.packageName, tag, event.getUidState(), event.getFlags(), startTime,
- event.getAttributionFlags(), event.getAttributionChainId());
+ mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op,
+ parent.uid, parent.packageName, tag, event.getUidState(), event.getFlags(),
+ startTime, event.getAttributionFlags(), event.getAttributionChainId());
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, true, event.getAttributionFlags(),
@@ -503,8 +503,8 @@
newEvent.mNumUnfinishedStarts += numPreviousUnfinishedStarts - 1;
}
} catch (RemoteException e) {
- if (AppOpsService.DEBUG) {
- Slog.e(AppOpsService.TAG,
+ if (AppOpsServiceImpl.DEBUG) {
+ Slog.e(AppOpsServiceImpl.TAG,
"Cannot switch to new uidState " + newState);
}
}
@@ -555,8 +555,8 @@
ArrayMap<IBinder, InProgressStartOpEvent> ignoredEvents =
opToAdd.isRunning()
? opToAdd.mInProgressEvents : opToAdd.mPausedInProgressEvents;
- Slog.w(AppOpsService.TAG, "Ignoring " + ignoredEvents.size() + " app-ops, running: "
- + opToAdd.isRunning());
+ Slog.w(AppOpsServiceImpl.TAG, "Ignoring " + ignoredEvents.size()
+ + " app-ops, running: " + opToAdd.isRunning());
int numInProgressEvents = ignoredEvents.size();
for (int i = 0; i < numInProgressEvents; i++) {
@@ -668,16 +668,22 @@
/**
* Create a new {@link InProgressStartOpEvent}.
*
- * @param startTime The time {@link #startOperation} was called
- * @param startElapsedTime The elapsed time when {@link #startOperation} was called
- * @param clientId The client id of the caller of {@link #startOperation}
+ * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
+ * was called
+ * @param startElapsedTime The elapsed time whe
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * @param clientId The client id of the caller of
+ * {@link AppOpCheckingServiceInterface#startOperation}
* @param attributionTag The attribution tag for the operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param uidState The uidstate of the app
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * for
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
- * @param proxy The proxy information, if {@link #startProxyOperation} was
- * called
+ * @param proxy The proxy information, if
+ * {@link AppOpCheckingServiceInterface#startProxyOperation} was
+ * called
* @param flags The trusted/nontrusted/self flags.
* @throws RemoteException If the client is dying
*/
@@ -718,15 +724,21 @@
/**
* Reinit existing object with new state.
*
- * @param startTime The time {@link #startOperation} was called
- * @param startElapsedTime The elapsed time when {@link #startOperation} was called
- * @param clientId The client id of the caller of {@link #startOperation}
+ * @param startTime The time {@link AppOpCheckingServiceInterface#startOperation}
+ * was called
+ * @param startElapsedTime The elapsed time when
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * @param clientId The client id of the caller of
+ * {@link AppOpCheckingServiceInterface#startOperation}
* @param attributionTag The attribution tag for this operation.
* @param onDeath The code to execute on client death
- * @param uidState The uidstate of the app {@link #startOperation} was called for
+ * @param uidState The uidstate of the app
+ * {@link AppOpCheckingServiceInterface#startOperation} was called
+ * for
* @param flags The flags relating to the proxy
- * @param proxy The proxy information, if {@link #startProxyOperation}
- * was called
+ * @param proxy The proxy information, if
+ * {@link AppOpCheckingServiceInterface#startProxyOperation was
+ * called
* @param attributionFlags the attribution flags for this operation.
* @param attributionChainId the unique id of the attribution chain this op is a part of.
* @param proxyPool The pool to release
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ae929c4..8aa898e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5863,6 +5863,9 @@
};
private boolean isValidCommunicationDevice(AudioDeviceInfo device) {
+ if (!device.isSink()) {
+ return false;
+ }
for (int type : VALID_COMMUNICATION_DEVICE_TYPES) {
if (device.getType() == type) {
return true;
@@ -5897,7 +5900,11 @@
throw new IllegalArgumentException("invalid portID " + portId);
}
if (!isValidCommunicationDevice(device)) {
- throw new IllegalArgumentException("invalid device type " + device.getType());
+ if (!device.isSink()) {
+ throw new IllegalArgumentException("device must have sink role");
+ } else {
+ throw new IllegalArgumentException("invalid device type: " + device.getType());
+ }
}
}
final String eventSource = new StringBuilder()
@@ -7092,9 +7099,10 @@
private @AudioManager.DeviceVolumeBehavior
int getDeviceVolumeBehaviorInt(@NonNull AudioDeviceAttributes device) {
- // translate Java device type to native device type (for the devices masks for full / fixed)
- final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice(
- device.getType());
+ // Get the internal type set by the AudioDeviceAttributes constructor which is always more
+ // exact (avoids double conversions) than a conversion from SDK type via
+ // AudioDeviceInfo.convertDeviceTypeToInternalDevice()
+ final int audioSystemDeviceOut = device.getInternalType();
int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut);
if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) {
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
new file mode 100644
index 0000000..06b45bf
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cpu;
+
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND;
+
+import com.android.internal.util.Preconditions;
+
+/** CPU availability information. */
+public final class CpuAvailabilityInfo {
+ /** Constant to indicate missing CPU availability percent. */
+ public static final int MISSING_CPU_AVAILABILITY_PERCENT = -1;
+
+ /**
+ * The CPUSET whose availability info is recorded in this object.
+ *
+ * <p>The contained value is one of the CPUSET_* constants from the
+ * {@link CpuAvailabilityMonitoringConfig}.
+ */
+ @CpuAvailabilityMonitoringConfig.Cpuset
+ public final int cpuset;
+
+ /** The latest average CPU availability percent. */
+ public final int latestAvgAvailabilityPercent;
+
+ /** The past N-second average CPU availability percent. */
+ public final int pastNSecAvgAvailabilityPercent;
+
+ /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */
+ public final int avgAvailabilityDurationSec;
+
+ @Override
+ public String toString() {
+ return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent="
+ + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent="
+ + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec="
+ + avgAvailabilityDurationSec + '}';
+ }
+
+ CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent,
+ int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) {
+ this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND,
+ "cpuset");
+ this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent;
+ this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent;
+ this.avgAvailabilityDurationSec = avgAvailabilityDurationSec;
+ }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
new file mode 100644
index 0000000..a3c4c9e
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java
@@ -0,0 +1,109 @@
+/*
+ * 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.cpu;
+
+import android.annotation.IntDef;
+import android.util.IntArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** CPU availability monitoring config. */
+public final class CpuAvailabilityMonitoringConfig {
+ /** Constant to monitor all cpusets. */
+ public static final int CPUSET_ALL = 1;
+
+ /** Constant to monitor background cpusets. */
+ public static final int CPUSET_BACKGROUND = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"CPUSET_"}, value = {
+ CPUSET_ALL,
+ CPUSET_BACKGROUND
+ })
+ public @interface Cpuset {
+ }
+
+ /**
+ * The CPUSET to monitor.
+ *
+ * <p>The value must be one of the {@code CPUSET_*} constants.
+ */
+ @Cpuset
+ public final int cpuset;
+
+ /**
+ * CPU availability percent thresholds.
+ *
+ * <p>CPU availability change notifications are sent when the latest or last N seconds average
+ * CPU availability percent crosses any of these thresholds since the last notification.
+ */
+ private final IntArray mThresholds;
+
+ public IntArray getThresholds() {
+ return mThresholds;
+ }
+
+ /**
+ * Builder for the construction of {@link CpuAvailabilityMonitoringConfig} objects.
+ *
+ * <p>The builder must contain at least one threshold before calling {@link build}.
+ */
+ public static final class Builder {
+ private final int mCpuset;
+ private final IntArray mThresholds = new IntArray();
+
+ public Builder(int cpuset, int... thresholds) {
+ mCpuset = cpuset;
+ for (int threshold : thresholds) {
+ addThreshold(threshold);
+ }
+ }
+
+ /** Adds the given threshold to the builder object. */
+ public Builder addThreshold(int threshold) {
+ if (mThresholds.indexOf(threshold) == -1) {
+ mThresholds.add(threshold);
+ }
+ return this;
+ }
+
+ /** Returns the {@link CpuAvailabilityMonitoringConfig} object. */
+ public CpuAvailabilityMonitoringConfig build() {
+ return new CpuAvailabilityMonitoringConfig(this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds
+ + ')';
+ }
+
+ private CpuAvailabilityMonitoringConfig(Builder builder) {
+ if (builder.mCpuset != CPUSET_ALL && builder.mCpuset != CPUSET_BACKGROUND) {
+ throw new IllegalStateException("Cpuset must be either CPUSET_ALL (" + CPUSET_ALL
+ + ") or CPUSET_BACKGROUND (" + CPUSET_BACKGROUND + "). Builder contains "
+ + builder.mCpuset);
+ }
+ if (builder.mThresholds.size() == 0) {
+ throw new IllegalStateException("Must provide at least one threshold");
+ }
+ this.cpuset = builder.mCpuset;
+ this.mThresholds = builder.mThresholds.clone();
+ }
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorInternal.java b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
new file mode 100644
index 0000000..849a20b
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorInternal.java
@@ -0,0 +1,81 @@
+/*
+ * 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.cpu;
+
+import android.annotation.CallbackExecutor;
+
+import java.util.concurrent.Executor;
+
+/** CpuMonitorInternal hosts internal APIs to monitor CPU. */
+public abstract class CpuMonitorInternal {
+ /** Callback to get CPU availability change notifications. */
+ public interface CpuAvailabilityCallback {
+ /**
+ * Called when the CPU availability crosses the provided thresholds.
+ *
+ * <p>Called when the latest or past N-second (which will be specified in the
+ * {@link CpuAvailabilityInfo}) average CPU availability percent has crossed
+ * (either goes above or drop below) the {@link CpuAvailabilityMonitoringConfig#thresholds}
+ * since the last notification. Also called when a callback is added to the service.
+ *
+ * <p>The callback is called at the executor which is specified in
+ * {@link addCpuAvailabilityCallback} or at the service handler thread.
+ *
+ * @param info CPU availability information.
+ */
+ void onAvailabilityChanged(CpuAvailabilityInfo info);
+
+ /**
+ * Called when the CPU monitoring interval changes.
+ *
+ * <p>Also called when a callback is added to the service.
+ *
+ * @param intervalMilliseconds CPU monitoring interval in milliseconds.
+ */
+ void onMonitoringIntervalChanged(long intervalMilliseconds);
+ }
+
+ /**
+ * Adds the {@link CpuAvailabilityCallback} for the caller.
+ *
+ * <p>When the callback is added, the callback will be called to notify the current CPU
+ * availability and monitoring interval.
+ *
+ * <p>When the client needs to update the {@link config} for a previously added callback,
+ * the client has to remove the callback and add the callback with a new {@link config}.
+ *
+ * @param executor Executor to execute the callback. If an executor is not provided,
+ * the callback will be executed on the service handler thread.
+ * @param config CPU availability monitoring config.
+ * @param callback Callback implementing {@link CpuAvailabilityCallback}
+ * interface.
+ *
+ * @throws IllegalStateException if {@code callback} is already added.
+ */
+ public abstract void addCpuAvailabilityCallback(@CallbackExecutor Executor executor,
+ CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback);
+
+ /**
+ * Removes the {@link CpuAvailabilityCallback} for the caller.
+ *
+ * @param callback Callback implementing {@link CpuAvailabilityCallback}
+ * interface.
+ *
+ * @throws IllegalArgumentException if {@code callback} is not previously added.
+ */
+ public abstract void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback);
+}
diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java
new file mode 100644
index 0000000..b0dfb84
--- /dev/null
+++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java
@@ -0,0 +1,173 @@
+/*
+ * 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.cpu;
+
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
+
+import android.content.Context;
+import android.os.Binder;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.DumpUtils;
+import com.android.server.SystemService;
+import com.android.server.utils.PriorityDump;
+import com.android.server.utils.Slogf;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/** Service to monitor CPU availability and usage. */
+public final class CpuMonitorService extends SystemService {
+ static final String TAG = CpuMonitorService.class.getSimpleName();
+ static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG);
+ // TODO(b/242722241): Make this a resource overlay property.
+ // Maintain 3 monitoring intervals:
+ // * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and
+ // CPU availability is above a threshold (such as at least 10% of CPU is available).
+ // * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available
+ // and CPU availability is below a threshold (such as less than 10% of CPU is available).
+ // * One to poll very less frequently when no callbacks are available and the build is either
+ // user-debug or eng. This will be useful for debugging in development environment.
+ static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000;
+
+ private final Context mContext;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo>
+ mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS;
+
+ private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() {
+ @Override
+ public void addCpuAvailabilityCallback(Executor executor,
+ CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) {
+ Objects.requireNonNull(callback, "Callback must be non-null");
+ Objects.requireNonNull(config, "Config must be non-null");
+ synchronized (mLock) {
+ if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+ Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s",
+ mCpuAvailabilityCallbackInfoByCallbacks.get(callback));
+ // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs)
+ // that maps callbacks based on the CPU availability thresholds.
+ }
+ CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config,
+ executor);
+ mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info);
+ if (DEBUG) {
+ Slogf.d(TAG, "Added a CPU availability callback: %s", info);
+ }
+ }
+ // TODO(b/242722241):
+ // * On the executor or on the handler thread, call the callback with the latest CPU
+ // availability info and monitoring interval.
+ // * Monitor the CPU stats more frequently when the first callback is added.
+ }
+
+ @Override
+ public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) {
+ synchronized (mLock) {
+ if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) {
+ Slogf.i(TAG, "CpuAvailabilityCallback was not previously added."
+ + " Ignoring the remove request");
+ return;
+ }
+ CpuAvailabilityCallbackInfo info =
+ mCpuAvailabilityCallbackInfoByCallbacks.remove(callback);
+ if (DEBUG) {
+ Slogf.d(TAG, "Removed a CPU availability callback: %s", info);
+ }
+ }
+ // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed.
+ }
+ };
+
+ public CpuMonitorService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ publishLocalService(CpuMonitorInternal.class, mLocalService);
+ publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false,
+ DUMP_FLAG_PRIORITY_CRITICAL);
+ }
+
+ private void doDump(IndentingPrintWriter writer) {
+ writer.printf("*%s*\n", getClass().getSimpleName());
+ writer.increaseIndent();
+ synchronized (mLock) {
+ writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds);
+ if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) {
+ writer.println("CPU availability change callbacks:");
+ writer.increaseIndent();
+ for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) {
+ writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i),
+ mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i));
+ }
+ writer.decreaseIndent();
+ }
+ }
+ // TODO(b/242722241): Print the recent past CPU stats.
+ writer.decreaseIndent();
+ }
+
+ private static final class CpuAvailabilityCallbackInfo {
+ public final CpuAvailabilityMonitoringConfig config;
+ public final Executor executor;
+
+ CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config,
+ Executor executor) {
+ this.config = config;
+ this.executor = executor;
+ }
+
+ @Override
+ public String toString() {
+ return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor
+ + '}';
+ }
+ }
+
+ private final class CpuMonitorBinder extends Binder {
+ private final PriorityDump.PriorityDumper mPriorityDumper =
+ new PriorityDump.PriorityDumper() {
+ @Override
+ public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
+ boolean asProto) {
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)
+ || asProto) {
+ return;
+ }
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw)) {
+ doDump(ipw);
+ }
+ }
+ };
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ PriorityDump.dump(mPriorityDumper, fd, pw, args);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 84dfe86..8811999 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -23,6 +23,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
import android.view.RoundedCorners;
import android.view.Surface;
@@ -158,6 +159,19 @@
public static final int FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 16;
/**
+ * Flag: Indicates that the display maintains its own focus and touch mode.
+ *
+ * This flag is similar to {@link com.android.internal.R.bool.config_perDisplayFocusEnabled} in
+ * behavior, but only applies to the specific display instead of system-wide to all displays.
+ *
+ * Note: The display must be trusted in order to have its own focus.
+ *
+ * @see #FLAG_TRUSTED
+ * @hide
+ */
+ public static final int FLAG_OWN_FOCUS = 1 << 17;
+
+ /**
* Touch attachment: Display does not receive touch.
*/
public static final int TOUCH_NONE = 0;
@@ -303,6 +317,11 @@
public RoundedCorners roundedCorners;
/**
+ * The {@link RoundedCorners} if present or {@code null} otherwise.
+ */
+ public DisplayShape displayShape;
+
+ /**
* The touch attachment, per {@link DisplayViewport#touch}.
*/
public int touch;
@@ -438,7 +457,8 @@
|| !BrightnessSynchronizer.floatEquals(brightnessDefault,
other.brightnessDefault)
|| !Objects.equals(roundedCorners, other.roundedCorners)
- || installOrientation != other.installOrientation) {
+ || installOrientation != other.installOrientation
+ || !Objects.equals(displayShape, other.displayShape)) {
diff |= DIFF_OTHER;
}
return diff;
@@ -484,6 +504,7 @@
brightnessDefault = other.brightnessDefault;
roundedCorners = other.roundedCorners;
installOrientation = other.installOrientation;
+ displayShape = other.displayShape;
}
// For debugging purposes
@@ -533,6 +554,9 @@
}
sb.append(flagsToString(flags));
sb.append(", installOrientation ").append(installOrientation);
+ if (displayShape != null) {
+ sb.append(", displayShape ").append(displayShape);
+ }
sb.append("}");
return sb.toString();
}
@@ -584,9 +608,30 @@
if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
}
+ if ((flags & FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+ msg.append(", FLAG_DESTROY_CONTENT_ON_REMOVAL");
+ }
if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
}
+ if ((flags & FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS) != 0) {
+ msg.append(", FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS");
+ }
+ if ((flags & FLAG_TRUSTED) != 0) {
+ msg.append(", FLAG_TRUSTED");
+ }
+ if ((flags & FLAG_OWN_DISPLAY_GROUP) != 0) {
+ msg.append(", FLAG_OWN_DISPLAY_GROUP");
+ }
+ if ((flags & FLAG_ALWAYS_UNLOCKED) != 0) {
+ msg.append(", FLAG_ALWAYS_UNLOCKED");
+ }
+ if ((flags & FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
+ msg.append(", FLAG_TOUCH_FEEDBACK_DISABLED");
+ }
+ if ((flags & FLAG_OWN_FOCUS) != 0) {
+ msg.append(", FLAG_OWN_FOCUS");
+ }
return msg.toString();
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 05cd67f..c5cb08d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -105,7 +105,6 @@
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
-import android.sysprop.DisplayProperties;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.EventLog;
@@ -451,8 +450,6 @@
}
};
- private final boolean mAllowNonNativeRefreshRateOverride;
-
private final BrightnessSynchronizer mBrightnessSynchronizer;
/**
@@ -506,7 +503,6 @@
ColorSpace[] colorSpaces = SurfaceControl.getCompositionColorSpaces();
mWideColorSpace = colorSpaces[1];
mOverlayProperties = SurfaceControl.getOverlaySupport();
- mAllowNonNativeRefreshRateOverride = mInjector.getAllowNonNativeRefreshRateOverride();
mSystemReady = false;
}
@@ -930,24 +926,20 @@
}
}
- if (mAllowNonNativeRefreshRateOverride) {
- overriddenInfo.refreshRateOverride = frameRateHz;
- if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
- callingUid)) {
- overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
- info.supportedModes.length + 1);
- overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
- new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
- currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
- overriddenInfo.refreshRateOverride);
- overriddenInfo.modeId =
- overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
- .getModeId();
- }
- return overriddenInfo;
+ overriddenInfo.refreshRateOverride = frameRateHz;
+ if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
+ callingUid)) {
+ overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
+ info.supportedModes.length + 1);
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
+ new Display.Mode(Display.DISPLAY_MODE_ID_FOR_FRAME_RATE_OVERRIDE,
+ currentMode.getPhysicalWidth(), currentMode.getPhysicalHeight(),
+ overriddenInfo.refreshRateOverride);
+ overriddenInfo.modeId =
+ overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1]
+ .getModeId();
}
-
- return info;
+ return overriddenInfo;
}
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
@@ -2602,11 +2594,6 @@
long getDefaultDisplayDelayTimeout() {
return WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
}
-
- boolean getAllowNonNativeRefreshRateOverride() {
- return DisplayProperties
- .debug_allow_non_native_refresh_rate_override().orElse(true);
- }
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 5a714f5..4bf1e98 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -38,6 +38,7 @@
import android.view.DisplayAddress;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
+import android.view.DisplayShape;
import android.view.RoundedCorners;
import android.view.SurfaceControl;
@@ -686,6 +687,9 @@
res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
+ mInfo.displayShape = DisplayShape.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+
if (mStaticDisplayInfo.isInternal) {
mInfo.type = Display.TYPE_INTERNAL;
mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index dedc56a..28bdce3 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -233,6 +233,7 @@
info.displayCutout = mOverrideDisplayInfo.displayCutout;
info.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi;
info.roundedCorners = mOverrideDisplayInfo.roundedCorners;
+ info.displayShape = mOverrideDisplayInfo.displayShape;
}
mInfo.set(info);
}
@@ -384,6 +385,9 @@
if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
}
+ if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
+ mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
+ }
Rect maskingInsets = getMaskingInsets(deviceInfo);
int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
@@ -434,6 +438,7 @@
mBaseDisplayInfo.brightnessDefault = deviceInfo.brightnessDefault;
mBaseDisplayInfo.roundedCorners = deviceInfo.roundedCorners;
mBaseDisplayInfo.installOrientation = deviceInfo.installOrientation;
+ mBaseDisplayInfo.displayShape = deviceInfo.displayShape;
mPrimaryDisplayDeviceInfo = deviceInfo;
mInfo.set(null);
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b0de844..0e11b53 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -29,6 +29,7 @@
import android.util.DisplayMetrics;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -361,6 +362,8 @@
mInfo.state = mState;
// The display is trusted since it is created by system.
mInfo.flags |= FLAG_TRUSTED;
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 20b82c3..d0e518b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -21,6 +21,7 @@
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
@@ -51,6 +52,7 @@
import android.util.ArrayMap;
import android.util.Slog;
import android.view.Display;
+import android.view.DisplayShape;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -495,13 +497,25 @@
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
mInfo.flags |= FLAG_TRUSTED;
}
- if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0
- && (mInfo.flags & DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP) != 0) {
- mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED) != 0) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
+ mInfo.flags |= FLAG_ALWAYS_UNLOCKED;
+ } else {
+ Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED as it "
+ + "requires VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP.");
+ }
}
if ((mFlags & VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
mInfo.flags |= FLAG_TOUCH_FEEDBACK_DISABLED;
}
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+ if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
+ mInfo.flags |= DisplayDeviceInfo.FLAG_OWN_FOCUS;
+ } else {
+ Slog.w(TAG, "Ignoring VIRTUAL_DISPLAY_FLAG_OWN_FOCUS as it requires "
+ + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+ }
+ }
mInfo.type = Display.TYPE_VIRTUAL;
mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
@@ -511,6 +525,9 @@
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
+
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index 146b003..c759d98 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -34,6 +34,7 @@
import android.util.Slog;
import android.view.Display;
import android.view.DisplayAddress;
+import android.view.DisplayShape;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -655,6 +656,8 @@
mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
// The display is trusted since it is created by system.
mInfo.flags |= DisplayDeviceInfo.FLAG_TRUSTED;
+ mInfo.displayShape =
+ DisplayShape.createDefaultDisplayShape(mInfo.width, mInfo.height, false);
}
return mInfo;
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
index 015e576..c53f1a5 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java
@@ -110,11 +110,10 @@
@AnyThread
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations,
- int configChanges, @InputMethodNavButtonFlags int navigationBarFlags) {
+ @InputMethodNavButtonFlags int navigationBarFlags) {
final IInputMethod.InitParams params = new IInputMethod.InitParams();
params.token = token;
params.privilegedOperations = privilegedOperations;
- params.configChanges = configChanges;
params.navigationBarFlags = navigationBarFlags;
try {
mTarget.initializeInternal(params);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 6dbb362..079234c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -42,12 +42,15 @@
import android.view.inputmethod.InputMethodInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IInputMethod;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.UnbindReason;
import com.android.server.EventLogTags;
import com.android.server.wm.WindowManagerInternal;
+import java.util.concurrent.CountDownLatch;
+
/**
* A controller managing the state of the input method binding.
*/
@@ -77,19 +80,26 @@
@GuardedBy("ImfLock.class") private boolean mVisibleBound;
@GuardedBy("ImfLock.class") private boolean mSupportsStylusHw;
+ @Nullable private CountDownLatch mLatchForTesting;
+
/**
* Binding flags for establishing connection to the {@link InputMethodService}.
*/
- private static final int IME_CONNECTION_BIND_FLAGS =
+ @VisibleForTesting
+ static final int IME_CONNECTION_BIND_FLAGS =
Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE
| Context.BIND_NOT_FOREGROUND
| Context.BIND_IMPORTANT_BACKGROUND
| Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
+ private final int mImeConnectionBindFlags;
+
/**
* Binding flags used only while the {@link InputMethodService} is showing window.
*/
- private static final int IME_VISIBLE_BIND_FLAGS =
+ @VisibleForTesting
+ static final int IME_VISIBLE_BIND_FLAGS =
Context.BIND_AUTO_CREATE
| Context.BIND_TREAT_LIKE_ACTIVITY
| Context.BIND_FOREGROUND_SERVICE
@@ -97,12 +107,19 @@
| Context.BIND_SHOWING_UI;
InputMethodBindingController(@NonNull InputMethodManagerService service) {
+ this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */);
+ }
+
+ InputMethodBindingController(@NonNull InputMethodManagerService service,
+ int imeConnectionBindFlags, CountDownLatch latchForTesting) {
mService = service;
mContext = mService.mContext;
mMethodMap = mService.mMethodMap;
mSettings = mService.mSettings;
mPackageManagerInternal = mService.mPackageManagerInternal;
mWindowManagerInternal = mService.mWindowManagerInternal;
+ mImeConnectionBindFlags = imeConnectionBindFlags;
+ mLatchForTesting = latchForTesting;
}
/**
@@ -242,7 +259,7 @@
@Override public void onBindingDied(ComponentName name) {
synchronized (ImfLock.class) {
mService.invalidateAutofillSessionLocked();
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
}
@@ -279,7 +296,7 @@
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
mSupportsStylusHw = info.supportsStylusHandwriting();
- mService.initializeImeLocked(mCurMethod, mCurToken, info.getConfigChanges());
+ mService.initializeImeLocked(mCurMethod, mCurToken);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
mService.reRequestCurrentClientSessionLocked();
mService.performOnCreateInlineSuggestionsRequestLocked();
@@ -291,6 +308,10 @@
mService.scheduleResetStylusHandwriting();
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+
+ if (mLatchForTesting != null) {
+ mLatchForTesting.countDown(); // Notify the finish to tests
+ }
}
@GuardedBy("ImfLock.class")
@@ -338,15 +359,15 @@
@GuardedBy("ImfLock.class")
void unbindCurrentMethod() {
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
- if (mHasConnection) {
+ if (hasConnection()) {
unbindMainConnection();
}
- if (mCurToken != null) {
+ if (getCurToken() != null) {
removeCurrentToken();
mService.resetSystemUiLocked();
}
@@ -448,17 +469,17 @@
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodService(ServiceConnection conn, int flags) {
- if (mCurIntent == null || conn == null) {
+ if (getCurIntent() == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn);
return false;
}
- return mContext.bindServiceAsUser(mCurIntent, conn, flags,
+ return mContext.bindServiceAsUser(getCurIntent(), conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
@GuardedBy("ImfLock.class")
private boolean bindCurrentInputMethodServiceMainConnection() {
- mHasConnection = bindCurrentInputMethodService(mMainConnection, IME_CONNECTION_BIND_FLAGS);
+ mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
return mHasConnection;
}
@@ -472,7 +493,7 @@
void setCurrentMethodVisible() {
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
- if (mHasConnection && !mVisibleBound) {
+ if (hasConnection() && !isVisibleBound()) {
mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
IME_VISIBLE_BIND_FLAGS);
}
@@ -480,7 +501,7 @@
}
// No IME is currently connected. Reestablish the main connection.
- if (!mHasConnection) {
+ if (!hasConnection()) {
if (DEBUG) {
Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
}
@@ -512,7 +533,7 @@
*/
@GuardedBy("ImfLock.class")
void setCurrentMethodNotVisible() {
- if (mVisibleBound) {
+ if (isVisibleBound()) {
unbindVisibleConnection();
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b083bd..080d582 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2692,14 +2692,13 @@
}
@GuardedBy("ImfLock.class")
- void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
- @android.content.pm.ActivityInfo.Config int configChanges) {
+ void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
+ mCurTokenDisplayId);
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
- configChanges, getInputMethodNavButtonFlagsLocked());
+ getInputMethodNavButtonFlagsLocked());
}
@AnyThread
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 8d247f6..3ce51c3 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -28,6 +28,7 @@
import static android.location.LocationManager.NETWORK_PROVIDER;
import static android.location.LocationRequest.LOW_POWER_EXCEPTIONS;
import static android.location.provider.LocationProviderBase.ACTION_FUSED_PROVIDER;
+import static android.location.provider.LocationProviderBase.ACTION_GNSS_PROVIDER;
import static android.location.provider.LocationProviderBase.ACTION_NETWORK_PROVIDER;
import static com.android.server.location.LocationPermissions.PERMISSION_COARSE;
@@ -439,9 +440,24 @@
mGnssManagerService = new GnssManagerService(mContext, mInjector, gnssNative);
mGnssManagerService.onSystemReady();
+ boolean useGnssHardwareProvider = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_useGnssHardwareProvider);
+ AbstractLocationProvider gnssProvider = null;
+ if (!useGnssHardwareProvider) {
+ gnssProvider = ProxyLocationProvider.create(
+ mContext,
+ GPS_PROVIDER,
+ ACTION_GNSS_PROVIDER,
+ com.android.internal.R.bool.config_useGnssHardwareProvider,
+ com.android.internal.R.string.config_gnssLocationProviderPackageName);
+ }
+ if (gnssProvider == null) {
+ gnssProvider = mGnssManagerService.getGnssLocationProvider();
+ }
+
LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector,
GPS_PROVIDER, mPassiveManager);
- addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider());
+ addLocationProviderManager(gnssManager, gnssProvider);
}
// bind to geocoder provider
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index f3cfa95..b8bdabe 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -213,7 +213,7 @@
final int appId = UserHandle.getAppId(pkg.getUid());
- String pkgSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ String pkgSeInfo = ps.getSeInfo();
Preconditions.checkNotNull(pkgSeInfo);
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
new file mode 100644
index 0000000..9ea350f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -0,0 +1,172 @@
+/*
+ * 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.pm;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.content.Context;
+import android.media.IAudioService;
+import android.os.ServiceManager;
+import android.text.TextUtils;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class to provide queries for app states concerning gentle-update.
+ */
+public class AppStateHelper {
+ private final Context mContext;
+
+ public AppStateHelper(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * True if the package is loaded into the process.
+ */
+ private static boolean isPackageLoaded(RunningAppProcessInfo info, String packageName) {
+ return ArrayUtils.contains(info.pkgList, packageName)
+ || ArrayUtils.contains(info.pkgDeps, packageName);
+ }
+
+ /**
+ * Returns the importance of the given package.
+ */
+ private int getImportance(String packageName) {
+ var am = mContext.getSystemService(ActivityManager.class);
+ return am.getPackageImportance(packageName);
+ }
+
+ /**
+ * True if the app owns the audio focus.
+ */
+ private boolean hasAudioFocus(String packageName) {
+ var audioService = IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+ try {
+ var focusInfos = audioService.getFocusStack();
+ int size = focusInfos.size();
+ var audioFocusPackage = (size > 0) ? focusInfos.get(size - 1).getPackageName() : null;
+ return TextUtils.equals(packageName, audioFocusPackage);
+ } catch (Exception ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * True if the app is in the foreground.
+ */
+ private boolean isAppForeground(String packageName) {
+ return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
+ }
+
+ /**
+ * True if the app is currently at the top of the screen that the user is interacting with.
+ */
+ public boolean isAppTopVisible(String packageName) {
+ return getImportance(packageName) <= RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ }
+
+ /**
+ * True if the app is playing/recording audio.
+ */
+ private boolean hasActiveAudio(String packageName) {
+ // TODO(b/235306967): also check recording
+ return hasAudioFocus(packageName);
+ }
+
+ /**
+ * True if the app is sending or receiving network data.
+ */
+ private boolean hasActiveNetwork(String packageName) {
+ // To be implemented
+ return false;
+ }
+
+ /**
+ * True if any app is interacting with the user.
+ */
+ public boolean hasInteractingApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (hasActiveAudio(packageName)
+ || hasActiveNetwork(packageName)
+ || isAppTopVisible(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if any app is in the foreground.
+ */
+ public boolean hasForegroundApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (isAppForeground(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if any app is top visible.
+ */
+ public boolean hasTopVisibleApp(List<String> packageNames) {
+ for (var packageName : packageNames) {
+ if (isAppTopVisible(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * True if there is an ongoing phone call.
+ */
+ public boolean isInCall() {
+ // To be implemented
+ return false;
+ }
+
+ /**
+ * Returns a list of packages which depend on {@code packageNames}. These are the packages
+ * that will be affected when updating {@code packageNames} and should participate in
+ * the evaluation of install constraints.
+ *
+ * TODO(b/235306967): Also include bounded services as dependency.
+ */
+ public List<String> getDependencyPackages(List<String> packageNames) {
+ var results = new ArraySet<String>();
+ var am = mContext.getSystemService(ActivityManager.class);
+ for (var info : am.getRunningAppProcesses()) {
+ for (var packageName : packageNames) {
+ if (!isPackageLoaded(info, packageName)) {
+ continue;
+ }
+ for (var pkg : info.pkgList) {
+ results.add(pkg);
+ }
+ }
+ }
+ return new ArrayList<>(results);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
new file mode 100644
index 0000000..247ac90
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java
@@ -0,0 +1,171 @@
+/*
+ * 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.pm;
+
+import android.annotation.WorkerThread;
+import android.app.ActivityThread;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageInstaller.InstallConstraints;
+import android.content.pm.PackageInstaller.InstallConstraintsResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Slog;
+
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A helper class to coordinate install flow for sessions with install constraints.
+ * These sessions will be pending and wait until the constraints are satisfied to
+ * resume installation.
+ */
+public class GentleUpdateHelper {
+ private static final String TAG = "GentleUpdateHelper";
+ private static final int JOB_ID = 235306967; // bug id
+ // The timeout used to determine whether the device is idle or not.
+ private static final long PENDING_CHECK_MILLIS = TimeUnit.SECONDS.toMillis(10);
+
+ /**
+ * A wrapper class used by JobScheduler to schedule jobs.
+ */
+ public static class Service extends JobService {
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ try {
+ var pis = (PackageInstallerService) ActivityThread.getPackageManager()
+ .getPackageInstaller();
+ var helper = pis.getGentleUpdateHelper();
+ helper.mHandler.post(helper::runIdleJob);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get PackageInstallerService", e);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+ }
+
+ private static class PendingInstallConstraintsCheck {
+ public final List<String> packageNames;
+ public final InstallConstraints constraints;
+ public final CompletableFuture<InstallConstraintsResult> future;
+ PendingInstallConstraintsCheck(List<String> packageNames,
+ InstallConstraints constraints,
+ CompletableFuture<InstallConstraintsResult> future) {
+ this.packageNames = packageNames;
+ this.constraints = constraints;
+ this.future = future;
+ }
+ }
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final AppStateHelper mAppStateHelper;
+ // Worker thread only
+ private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
+ private boolean mHasPendingIdleJob;
+
+ GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
+ mContext = context;
+ mHandler = new Handler(looper);
+ mAppStateHelper = appStateHelper;
+ }
+
+ /**
+ * Checks if install constraints are satisfied for the given packages.
+ */
+ CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
+ List<String> packageNames, InstallConstraints constraints) {
+ var future = new CompletableFuture<InstallConstraintsResult>();
+ mHandler.post(() -> {
+ var pendingCheck = new PendingInstallConstraintsCheck(
+ packageNames, constraints, future);
+ if (constraints.isRequireDeviceIdle()) {
+ mPendingChecks.add(pendingCheck);
+ // JobScheduler doesn't provide queries about whether the device is idle.
+ // We schedule 2 tasks to determine device idle. If the idle job is executed
+ // before the delayed runnable, we know the device is idle.
+ // Note #processPendingCheck will be no-op for the task executed later.
+ scheduleIdleJob();
+ mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
+ PENDING_CHECK_MILLIS);
+ } else {
+ processPendingCheck(pendingCheck, false);
+ }
+ });
+ return future;
+ }
+
+ @WorkerThread
+ private void scheduleIdleJob() {
+ if (mHasPendingIdleJob) {
+ // No need to schedule the job again
+ return;
+ }
+ mHasPendingIdleJob = true;
+ var componentName = new ComponentName(
+ mContext.getPackageName(), GentleUpdateHelper.Service.class.getName());
+ var jobInfo = new JobInfo.Builder(JOB_ID, componentName)
+ .setRequiresDeviceIdle(true)
+ .build();
+ var jobScheduler = mContext.getSystemService(JobScheduler.class);
+ jobScheduler.schedule(jobInfo);
+ }
+
+ @WorkerThread
+ private void runIdleJob() {
+ mHasPendingIdleJob = false;
+ processPendingChecksInIdle();
+ }
+
+ @WorkerThread
+ private void processPendingCheck(PendingInstallConstraintsCheck pendingCheck, boolean isIdle) {
+ var future = pendingCheck.future;
+ if (future.isDone()) {
+ return;
+ }
+ var constraints = pendingCheck.constraints;
+ var packageNames = mAppStateHelper.getDependencyPackages(pendingCheck.packageNames);
+ var constraintsSatisfied = (!constraints.isRequireDeviceIdle() || isIdle)
+ && (!constraints.isRequireAppNotForeground()
+ || !mAppStateHelper.hasForegroundApp(packageNames))
+ && (!constraints.isRequireAppNotInteracting()
+ || !mAppStateHelper.hasInteractingApp(packageNames))
+ && (!constraints.isRequireAppNotTopVisible()
+ || !mAppStateHelper.hasTopVisibleApp(packageNames))
+ && (!constraints.isRequireNotInCall()
+ || !mAppStateHelper.isInCall());
+ future.complete(new InstallConstraintsResult((constraintsSatisfied)));
+ }
+
+ @WorkerThread
+ private void processPendingChecksInIdle() {
+ while (!mPendingChecks.isEmpty()) {
+ processPendingCheck(mPendingChecks.remove(), true);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 78e4190..283640d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -765,7 +765,7 @@
|| (installFlags & PackageManager.INSTALL_REQUEST_DOWNGRADE) != 0);
if (ps != null && doSnapshotOrRestore) {
- final String seInfo = AndroidPackageUtils.getSeInfo(request.getPkg(), ps);
+ final String seInfo = ps.getSeInfo();
final RollbackManagerInternal rollbackManager =
mInjector.getLocalService(RollbackManagerInternal.class);
rollbackManager.snapshotAndRestoreUserData(packageName,
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b27373e..b66c6ac 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -129,7 +129,7 @@
final InstallSource installSource = packageState.getInstallSource();
final String packageAbiOverride = packageState.getCpuAbiOverride();
final int appId = UserHandle.getAppId(pkg.getUid());
- final String seinfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+ final String seinfo = packageState.getSeInfo();
final String label = String.valueOf(pm.getApplicationLabel(
AndroidPackageUtils.generateAppInfoWithoutState(pkg)));
final int targetSdkVersion = pkg.getTargetSdkVersion();
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 226a27e..49f3a3c 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -493,7 +493,7 @@
// TODO: Consider adding 2 different APIs for primary and secondary dexopt.
// installd only uses downgrade flag for secondary dex files and ignores it for
// primary dex files.
- String seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+ String seInfo = pkgSetting.getSeInfo();
boolean completed = getInstallerLI().dexopt(path, uid, pkg.getPackageName(), isa,
dexoptNeeded, oatDir, dexoptFlags, compilerFilter, pkg.getVolumeUuid(),
classLoaderContext, seInfo, /* downgrade= */ false ,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 653a882..409d352 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -44,6 +44,7 @@
import android.content.pm.IPackageInstallerSession;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallConstraints;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageItemInfo;
@@ -54,12 +55,14 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SELinux;
@@ -88,6 +91,7 @@
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.ImageUtils;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.IoThread;
@@ -186,6 +190,7 @@
private final InternalCallback mInternalCallback = new InternalCallback();
private final PackageSessionVerifier mSessionVerifier;
+ private final GentleUpdateHelper mGentleUpdateHelper;
/**
* Used for generating session IDs. Since this is created at boot time,
@@ -272,6 +277,8 @@
mStagingManager = new StagingManager(context);
mSessionVerifier = new PackageSessionVerifier(context, mPm, mApexManager,
apexParserSupplier, mInstallThread.getLooper());
+ mGentleUpdateHelper = new GentleUpdateHelper(
+ context, mInstallThread.getLooper(), new AppStateHelper(context));
LocalServices.getService(SystemServiceManager.class).startService(
new Lifecycle(context, this));
@@ -1233,6 +1240,33 @@
}
@Override
+ public void checkInstallConstraints(String installerPackageName, List<String> packageNames,
+ InstallConstraints constraints, RemoteCallback callback) {
+ Preconditions.checkArgument(packageNames != null);
+ Preconditions.checkArgument(constraints != null);
+ Preconditions.checkArgument(callback != null);
+
+ final var snapshot = mPm.snapshotComputer();
+ final int callingUid = Binder.getCallingUid();
+ if (!isCalledBySystemOrShell(callingUid)) {
+ for (var packageName : packageNames) {
+ var ps = snapshot.getPackageStateInternal(packageName);
+ if (ps == null || !TextUtils.equals(
+ ps.getInstallSource().mInstallerPackageName, installerPackageName)) {
+ throw new SecurityException("Caller has no access to package " + packageName);
+ }
+ }
+ }
+
+ var future = mGentleUpdateHelper.checkInstallConstraints(packageNames, constraints);
+ future.thenAccept(result -> {
+ var b = new Bundle();
+ b.putParcelable("result", result);
+ callback.sendResult(b);
+ });
+ }
+
+ @Override
public void registerCallback(IPackageInstallerCallback callback, int userId) {
final Computer snapshot = mPm.snapshotComputer();
snapshot.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
@@ -1265,6 +1299,11 @@
}
@Override
+ public GentleUpdateHelper getGentleUpdateHelper() {
+ return mGentleUpdateHelper;
+ }
+
+ @Override
public void bypassNextStagedInstallerCheck(boolean value) {
if (!isCalledBySystemOrShell(Binder.getCallingUid())) {
throw new SecurityException("Caller not allowed to bypass staged installer check");
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8f8cc8a..9e1bffb 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1560,7 +1560,7 @@
AndroidPackage pkg = packageState.getPkg();
SharedUserApi sharedUser = snapshot.getSharedUser(
packageState.getSharedUserAppId());
- String oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, packageState);
+ String oldSeInfo = packageState.getSeInfo();
if (pkg == null) {
Slog.e(TAG, "Failed to find package " + packageName);
diff --git a/services/core/java/com/android/server/pm/PackageSessionProvider.java b/services/core/java/com/android/server/pm/PackageSessionProvider.java
index ad5cf13..79b88b3 100644
--- a/services/core/java/com/android/server/pm/PackageSessionProvider.java
+++ b/services/core/java/com/android/server/pm/PackageSessionProvider.java
@@ -29,4 +29,9 @@
PackageInstallerSession getSession(int sessionId);
PackageSessionVerifier getSessionVerifier();
+
+ /**
+ * Get the GentleUpdateHelper instance.
+ */
+ GentleUpdateHelper getGentleUpdateHelper();
}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6d90593..3ec6e7d 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1358,6 +1358,17 @@
}
@Nullable
+ @Override
+ public String getSeInfo() {
+ String overrideSeInfo = getTransientState().getOverrideSeInfo();
+ if (!TextUtils.isEmpty(overrideSeInfo)) {
+ return overrideSeInfo;
+ }
+
+ return getTransientState().getSeInfo();
+ }
+
+ @Nullable
public String getPrimaryCpuAbiLegacy() {
return mPrimaryCpuAbi;
}
@@ -1518,10 +1529,10 @@
}
@DataClass.Generated(
- time = 1662666062860L,
+ time = 1665779003744L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
- inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+ inputSignatures = "private int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate float mLoadingProgress\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate long mLastModifiedTime\nprivate long lastUpdateTime\nprivate long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate int categoryOverride\nprivate boolean updateAvailable\nprivate boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic com.android.server.pm.PackageSetting snapshot()\npublic void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic com.android.server.pm.PackageSetting setAppId(int)\npublic com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic com.android.server.pm.PackageSetting setInstallerPackageName(java.lang.String)\npublic com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic void updateFrom(com.android.server.pm.PackageSetting)\n com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic boolean isPrivileged()\npublic boolean isOem()\npublic boolean isVendor()\npublic boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic boolean isSystemExt()\npublic boolean isOdm()\npublic boolean isSystem()\npublic android.content.pm.SigningDetails getSigningDetails()\npublic com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n void setEnabled(int,int,java.lang.String)\n int getEnabled(int)\n void setInstalled(boolean,int)\n boolean getInstalled(int)\n int getInstallReason(int)\n void setInstallReason(int,int)\n int getUninstallReason(int)\n void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n boolean isAnyInstalled(int[])\n int[] queryInstalledUsers(int[],boolean)\n long getCeDataInode(int)\n void setCeDataInode(long,int)\n boolean getStopped(int)\n void setStopped(boolean,int)\n boolean getNotLaunched(int)\n void setNotLaunched(boolean,int)\n boolean getHidden(int)\n void setHidden(boolean,int)\n int getDistractionFlags(int)\n void setDistractionFlags(int,int)\npublic boolean getInstantApp(int)\n void setInstantApp(boolean,int)\n boolean getVirtualPreload(int)\n void setVirtualPreload(boolean,int)\n void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long)\n void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n void addDisabledComponent(java.lang.String,int)\n void addEnabledComponent(java.lang.String,int)\n boolean enableComponentLPw(java.lang.String,int)\n boolean disableComponentLPw(java.lang.String,int)\n boolean restoreComponentLPw(java.lang.String,int)\n int getCurrentEnabledStateLPr(java.lang.String,int)\n void removeUser(int)\npublic int[] getNotInstalledUserIds()\n void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\n com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic boolean isLoading()\npublic com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getUsesLibraries()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index a905df9..6572d7b 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,8 +265,8 @@
pkgSetting.getPkgState().setUpdatedSystemApp(true);
}
- parsedPackage.setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage, sharedUserSetting,
- injector.getCompatibility()));
+ pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+ sharedUserSetting, injector.getCompatibility()));
if (parsedPackage.isSystem()) {
configurePackageComponents(parsedPackage);
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index a40d404..4aba016 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,7 +102,6 @@
import com.android.server.backup.PreferredActivityBackupHelper;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.permission.LegacyPermissionDataProvider;
import com.android.server.pm.permission.LegacyPermissionSettings;
import com.android.server.pm.permission.LegacyPermissionState;
@@ -2900,7 +2899,7 @@
sb.append(isDebug ? " 1 " : " 0 ");
sb.append(dataPath);
sb.append(" ");
- sb.append(AndroidPackageUtils.getSeInfo(pkg.getPkg(), pkg));
+ sb.append(pkg.getSeInfo());
sb.append(" ");
final int gidsSize = gids.size();
if (gids != null && gids.size() > 0) {
@@ -4359,7 +4358,7 @@
// (CE storage is not ready yet; the CE data directories will be created later,
// when the user is "unlocked".) Accumulate all required args, and call the
// installer after the mPackages lock has been released.
- final String seInfo = AndroidPackageUtils.getSeInfo(ps.getPkg(), ps);
+ final String seInfo = ps.getSeInfo();
final boolean usesSdk = !ps.getPkg().getUsesSdkLibraries().isEmpty();
final CreateAppDataArgs args = Installer.buildCreateAppDataArgs(
ps.getVolumeUuid(), ps.getPackageName(), userHandle,
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 1da442b..74594cc 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -53,7 +53,6 @@
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.pm.pkg.PackageStateUtils;
@@ -347,7 +346,7 @@
// an update, and hence need to restore data for all installed users.
final int[] installedUsers = PackageStateUtils.queryInstalledUsers(ps, allUsers, true);
- final String seInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
+ final String seInfo = ps.getSeInfo();
rm.snapshotAndRestoreUserData(packageName, UserHandle.toUserHandles(installedUsers),
appId, ceDataInode, seInfo, 0 /*token*/);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 1027f4c..0f920c6 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -435,10 +435,20 @@
/** Removes a {@link UserVisibilityListener}. */
public abstract void removeUserVisibilityListener(UserVisibilityListener listener);
- /** TODO(b/244333150): temporary method until UserVisibilityMediator handles that logic */
- public abstract void onUserVisibilityChanged(@UserIdInt int userId, boolean visible);
+ // TODO(b/242195409): remove this method if not needed anymore
+ /** Notify {@link UserVisibilityListener listeners} that the visibility of the
+ * {@link android.os.UserHandle#USER_SYSTEM} changed. */
+ public abstract void onSystemUserVisibilityChanged(boolean visible);
/** Return the integer types of the given user IDs. Only used for reporting metrics to statsd.
*/
public abstract int[] getUserTypesForStatsd(@UserIdInt int[] userIds);
+
+ /**
+ * Returns the user id of the main user, or {@link android.os.UserHandle#USER_NULL} if there is
+ * no main user.
+ *
+ * @see UserManager#isMainUser()
+ */
+ public abstract @UserIdInt int getMainUserId();
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 23a6b67..3234e87 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -97,7 +97,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
-import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Slog;
@@ -126,7 +125,6 @@
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemService;
-import com.android.server.am.EventLogTags;
import com.android.server.am.UserState;
import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
@@ -191,6 +189,7 @@
private static final String ATTR_CREATION_TIME = "created";
private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
private static final String ATTR_LAST_LOGGED_IN_FINGERPRINT = "lastLoggedInFingerprint";
+ private static final String ATTR_LAST_ENTERED_FOREGROUND_TIME = "lastEnteredForeground";
private static final String ATTR_SERIAL_NO = "serialNumber";
private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
private static final String ATTR_PARTIAL = "partial";
@@ -341,6 +340,9 @@
/** Elapsed realtime since boot when the user was unlocked. */
long unlockRealtime;
+ /** Wall clock time in millis when the user last entered the foreground. */
+ long mLastEnteredForegroundTimeMillis;
+
private long mLastRequestQuietModeEnabledMillis;
/**
@@ -508,10 +510,6 @@
@GuardedBy("mUserLifecycleListeners")
private final ArrayList<UserLifecycleListener> mUserLifecycleListeners = new ArrayList<>();
- // TODO(b/244333150): temporary array, should belong to UserVisibilityMediator
- @GuardedBy("mUserVisibilityListeners")
- private final ArrayList<UserVisibilityListener> mUserVisibilityListeners = new ArrayList<>();
-
private final LockPatternUtils mLockPatternUtils;
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
@@ -680,6 +678,10 @@
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
if (user != null) {
user.startRealtime = SystemClock.elapsedRealtime();
+ if (targetUser.getUserIdentifier() == UserHandle.USER_SYSTEM
+ && targetUser.isFull()) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
}
}
}
@@ -695,6 +697,16 @@
}
@Override
+ public void onUserSwitching(@NonNull TargetUser from, @NonNull TargetUser to) {
+ synchronized (mUms.mUsersLock) {
+ final UserData user = mUms.getUserDataLU(to.getUserIdentifier());
+ if (user != null) {
+ mUms.setLastEnteredForegroundTimeToNow(user);
+ }
+ }
+ }
+
+ @Override
public void onUserStopping(@NonNull TargetUser targetUser) {
synchronized (mUms.mUsersLock) {
final UserData user = mUms.getUserDataLU(targetUser.getUserIdentifier());
@@ -901,6 +913,49 @@
return null;
}
+ @Override
+ public @UserIdInt int getMainUserId() {
+ checkQueryOrCreateUsersPermission("get main user id");
+ return getMainUserIdUnchecked();
+ }
+
+ private @UserIdInt int getMainUserIdUnchecked() {
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserInfo user = mUsers.valueAt(i).info;
+ if (user.isMain() && !mRemovingUserIds.get(user.id)) {
+ return user.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
+ @Override
+ public int getPreviousFullUserToEnterForeground() {
+ checkQueryOrCreateUsersPermission("get previous user");
+ int previousUser = UserHandle.USER_NULL;
+ long latestEnteredTime = 0;
+ final int currentUser = getCurrentUserId();
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ final int userId = userData.info.id;
+ if (userId != currentUser && userData.info.isFull() && !userData.info.partial
+ && !mRemovingUserIds.get(userId)) {
+ final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis;
+ if (userEnteredTime > latestEnteredTime) {
+ latestEnteredTime = userEnteredTime;
+ previousUser = userId;
+ }
+ }
+ }
+ }
+ return previousUser;
+ }
+
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
@@ -3339,13 +3394,13 @@
Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): no system user data");
return;
}
+ final int oldMainUserId = getMainUserIdUnchecked();
final int oldFlags = systemUserData.info.flags;
final int newFlags;
final String newUserType;
- // TODO(b/256624031): Also handle FLAG_MAIN
if (newHeadlessSystemUserMode) {
newUserType = UserManager.USER_TYPE_SYSTEM_HEADLESS;
- newFlags = oldFlags & ~UserInfo.FLAG_FULL;
+ newFlags = oldFlags & ~UserInfo.FLAG_FULL & ~UserInfo.FLAG_MAIN;
} else {
newUserType = UserManager.USER_TYPE_FULL_SYSTEM;
newFlags = oldFlags | UserInfo.FLAG_FULL;
@@ -3360,9 +3415,38 @@
+ "%s, flags changed from %s to %s",
systemUserData.info.userType, newUserType,
UserInfo.flagsToString(oldFlags), UserInfo.flagsToString(newFlags));
+
systemUserData.info.userType = newUserType;
systemUserData.info.flags = newFlags;
writeUserLP(systemUserData);
+
+ // Switch the MainUser to a reasonable choice if needed.
+ // (But if there was no MainUser, we deliberately continue to have no MainUser.)
+ final UserData oldMain = getUserDataNoChecks(oldMainUserId);
+ if (newHeadlessSystemUserMode) {
+ if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) != 0) {
+ // System was MainUser. So we need a new choice for Main. Pick the oldest.
+ // If no oldest, don't set any. Let the BootUserInitializer do that later.
+ final UserInfo newMainUser = getEarliestCreatedFullUser();
+ if (newMainUser != null) {
+ Slogf.i(LOG_TAG, "Designating user " + newMainUser.id + " to be Main");
+ newMainUser.flags |= UserInfo.FLAG_MAIN;
+ writeUserLP(getUserDataNoChecks(newMainUser.id));
+ }
+ }
+ } else {
+ // TODO(b/256624031): For now, we demand the Main user (if there is one) is
+ // always the system in non-HSUM. In the future, when we relax this, change how
+ // we handle MAIN.
+ if (oldMain != null && (oldMain.info.flags & UserInfo.FLAG_SYSTEM) == 0) {
+ // Someone else was the MainUser; transfer it to System.
+ Slogf.i(LOG_TAG, "Transferring Main to user 0 from " + oldMain.info.id);
+ oldMain.info.flags &= ~UserInfo.FLAG_MAIN;
+ systemUserData.info.flags |= UserInfo.FLAG_MAIN;
+ writeUserLP(oldMain);
+ writeUserLP(systemUserData);
+ }
+ }
}
}
@@ -3639,8 +3723,10 @@
// Add FLAG_MAIN
if (isHeadlessSystemUserMode()) {
final UserInfo earliestCreatedUser = getEarliestCreatedFullUser();
- earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
- userIdsToWrite.add(earliestCreatedUser.id);
+ if (earliestCreatedUser != null) {
+ earliestCreatedUser.flags |= UserInfo.FLAG_MAIN;
+ userIdsToWrite.add(earliestCreatedUser.id);
+ }
} else {
synchronized (mUsersLock) {
final UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
@@ -3780,9 +3866,10 @@
userInfo.profileBadge = getFreeProfileBadgeLU(userInfo.profileGroupId, userInfo.userType);
}
- private UserInfo getEarliestCreatedFullUser() {
+ /** Returns the oldest Full Admin user, or null is if there none. */
+ private @Nullable UserInfo getEarliestCreatedFullUser() {
final List<UserInfo> users = getUsersInternal(true, true, true);
- UserInfo earliestUser = users.get(0);
+ UserInfo earliestUser = null;
long earliestCreationTime = Long.MAX_VALUE;
for (int i = 0; i < users.size(); i++) {
final UserInfo info = users.get(i);
@@ -3922,6 +4009,8 @@
serializer.attribute(null, ATTR_LAST_LOGGED_IN_FINGERPRINT,
userInfo.lastLoggedInFingerprint);
}
+ serializer.attributeLong(
+ null, ATTR_LAST_ENTERED_FOREGROUND_TIME, userData.mLastEnteredForegroundTimeMillis);
if (userInfo.iconPath != null) {
serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath);
}
@@ -4095,6 +4184,7 @@
long lastLoggedInTime = 0L;
long lastRequestQuietModeEnabledTimestamp = 0L;
String lastLoggedInFingerprint = null;
+ long lastEnteredForegroundTime = 0L;
int profileGroupId = UserInfo.NO_PROFILE_GROUP_ID;
int profileBadge = 0;
int restrictedProfileParentId = UserInfo.NO_PROFILE_GROUP_ID;
@@ -4140,6 +4230,8 @@
lastLoggedInTime = parser.getAttributeLong(null, ATTR_LAST_LOGGED_IN_TIME, 0);
lastLoggedInFingerprint = parser.getAttributeValue(null,
ATTR_LAST_LOGGED_IN_FINGERPRINT);
+ lastEnteredForegroundTime =
+ parser.getAttributeLong(null, ATTR_LAST_ENTERED_FOREGROUND_TIME, 0L);
profileGroupId = parser.getAttributeInt(null, ATTR_PROFILE_GROUP_ID,
UserInfo.NO_PROFILE_GROUP_ID);
profileBadge = parser.getAttributeInt(null, ATTR_PROFILE_BADGE, 0);
@@ -4234,6 +4326,7 @@
userData.seedAccountOptions = seedAccountOptions;
userData.userProperties = userProperties;
userData.setLastRequestQuietModeEnabledMillis(lastRequestQuietModeEnabledTimestamp);
+ userData.mLastEnteredForegroundTimeMillis = lastEnteredForegroundTime;
if (ignorePrepareStorageErrors) {
userData.setIgnorePrepareStorageErrors();
}
@@ -6153,6 +6246,11 @@
|| someUserHasSeedAccountNoChecks(accountName, accountType));
}
+ private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
+ userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
+ scheduleWriteUser(userData);
+ }
+
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
@@ -6279,9 +6377,6 @@
synchronized (mUserLifecycleListeners) {
pw.println(" user lifecycle events: " + mUserLifecycleListeners.size());
}
- synchronized (mUserVisibilityListeners) {
- pw.println(" user visibility events: " + mUserVisibilityListeners.size());
- }
// Dump UserTypes
pw.println();
@@ -6377,6 +6472,9 @@
pw.print(" Unlock time: ");
dumpTimeAgo(pw, tempStringBuilder, nowRealtime, userData.unlockRealtime);
+ pw.print(" Last entered foreground: ");
+ dumpTimeAgo(pw, tempStringBuilder, now, userData.mLastEnteredForegroundTimeMillis);
+
pw.print(" Has profile owner: ");
pw.println(mIsUserManaged.get(userId));
pw.println(" Restrictions:");
@@ -6854,31 +6952,17 @@
@Override
public void addUserVisibilityListener(UserVisibilityListener listener) {
- synchronized (mUserVisibilityListeners) {
- mUserVisibilityListeners.add(listener);
- }
+ mUserVisibilityMediator.addListener(listener);
}
@Override
public void removeUserVisibilityListener(UserVisibilityListener listener) {
- synchronized (mUserVisibilityListeners) {
- mUserVisibilityListeners.remove(listener);
- }
+ mUserVisibilityMediator.removeListener(listener);
}
@Override
- public void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
- mHandler.post(() -> {
- UserVisibilityListener[] listeners;
- synchronized (mUserVisibilityListeners) {
- listeners = new UserVisibilityListener[mUserVisibilityListeners.size()];
- mUserVisibilityListeners.toArray(listeners);
- }
- for (UserVisibilityListener listener : listeners) {
- listener.onUserVisibilityChanged(userId, visible);
- }
- });
+ public void onSystemUserVisibilityChanged(boolean visible) {
+ mUserVisibilityMediator.onSystemUserVisibilityChanged(visible);
}
@Override
@@ -6898,6 +6982,12 @@
}
return userTypes;
}
+
+ @Override
+ public @UserIdInt int getMainUserId() {
+ return getMainUserIdUnchecked();
+ }
+
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 2650b23..9b9ca10 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -32,6 +32,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Dumpable;
+import android.util.EventLog;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.SparseIntArray;
@@ -40,6 +41,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
+import com.android.server.am.EventLogTags;
import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
import com.android.server.utils.Slogf;
@@ -68,6 +70,7 @@
public final class UserVisibilityMediator implements Dumpable {
private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+ private static final boolean VERBOSE = false; // DO NOT SUBMIT WITH TRUE
private static final String TAG = UserVisibilityMediator.class.getSimpleName();
@@ -381,8 +384,8 @@
public boolean isUserVisible(@UserIdInt int userId) {
// First check current foreground user and their profiles (on main display)
if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
- if (DBG) {
- Slogf.d(TAG, "isUserVisible(%d): true to current user or profile", userId);
+ if (VERBOSE) {
+ Slogf.v(TAG, "isUserVisible(%d): true to current user or profile", userId);
}
return true;
}
@@ -517,6 +520,14 @@
}
}
+ // TODO(b/242195409): remove this method if not needed anymore
+ /**
+ * Nofify all listeners that the system user visibility changed.
+ */
+ void onSystemUserVisibilityChanged(boolean visible) {
+ dispatchVisibilityChanged(mListeners, USER_SYSTEM, visible);
+ }
+
/**
* Nofify all listeners about the visibility changes from before / after a change of state.
*/
@@ -534,7 +545,7 @@
Slogf.d(TAG,
"dispatchVisibilityChanged(): visibleUsersBefore=%s, visibleUsersAfter=%s, "
+ "%d listeners (%s)", visibleUsersBefore, visibleUsersAfter, listeners.size(),
- mListeners);
+ listeners);
}
for (int i = 0; i < visibleUsersBefore.size(); i++) {
int userId = visibleUsersBefore.get(i);
@@ -552,13 +563,14 @@
private void dispatchVisibilityChanged(CopyOnWriteArrayList<UserVisibilityListener> listeners,
@UserIdInt int userId, boolean visible) {
+ EventLog.writeEvent(EventLogTags.UM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
if (DBG) {
Slogf.d(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %d listeners",
userId, visible, listeners.size());
}
for (int i = 0; i < mListeners.size(); i++) {
UserVisibilityListener listener = mListeners.get(i);
- if (DBG) {
+ if (VERBOSE) {
Slogf.v(TAG, "dispatchVisibilityChanged(%d -> %b): sending to %s",
userId, visible, listener);
}
@@ -575,9 +587,7 @@
ipw.println(mCurrentUserId);
ipw.print("Visible users: ");
- // TODO: merge 2 lines below if/when IntArray implements toString()...
- IntArray visibleUsers = getVisibleUsers();
- ipw.println(java.util.Arrays.toString(visibleUsers.toArray()));
+ ipw.println(getVisibleUsers());
dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
"u", "pg");
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a7d4cea..558202b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -452,7 +452,7 @@
info.category = pkgSetting.getCategoryOverride();
}
- info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
+ info.seInfo = pkgSetting.getSeInfo();
info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 944e4ad..876bf17 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -40,13 +40,6 @@
String getPrimaryCpuAbi();
/**
- * @see ApplicationInfo#seInfo
- * TODO: This field is deriveable and might not have to be cached here.
- */
- @Nullable
- String getSeInfo();
-
- /**
* @see ApplicationInfo#secondaryCpuAbi
*/
@Nullable
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 5b0cc51..c76b129 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -27,7 +27,6 @@
import android.content.pm.parsing.result.ParseResult;
import android.content.pm.parsing.result.ParseTypeImpl;
import android.os.incremental.IncrementalManager;
-import android.text.TextUtils;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.util.ArrayUtils;
@@ -288,16 +287,6 @@
return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
}
- public static String getSeInfo(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
- if (pkgSetting != null) {
- String overrideSeInfo = pkgSetting.getTransientState().getOverrideSeInfo();
- if (!TextUtils.isEmpty(overrideSeInfo)) {
- return overrideSeInfo;
- }
- }
- return ((AndroidPackageHidden) pkg).getSeInfo();
- }
-
@Deprecated
@NonNull
public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index a43b979..ba36ab7 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -462,10 +462,6 @@
@DataClass.ParcelWith(ForInternedString.class)
protected String secondaryNativeLibraryDir;
- @Nullable
- @DataClass.ParcelWith(ForInternedString.class)
- protected String seInfo;
-
/**
* This is an appId, the uid if the userId is == USER_SYSTEM
*/
@@ -1339,6 +1335,11 @@
}
@Override
+ public UUID getStorageUuid() {
+ return mStorageUuid;
+ }
+
+ @Override
public int getTargetSandboxVersion() {
return targetSandboxVersion;
}
@@ -2905,12 +2906,6 @@
}
@Override
- public PackageImpl setSeInfo(@Nullable String seInfo) {
- this.seInfo = TextUtils.safeIntern(seInfo);
- return this;
- }
-
- @Override
public PackageImpl setSplitCodePaths(@Nullable String[] splitCodePaths) {
this.splitCodePaths = splitCodePaths;
if (splitCodePaths != null) {
@@ -2993,7 +2988,6 @@
appInfo.primaryCpuAbi = primaryCpuAbi;
appInfo.secondaryCpuAbi = secondaryCpuAbi;
appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
- appInfo.seInfo = seInfo;
appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
appInfo.uid = uid;
return appInfo;
@@ -3147,7 +3141,6 @@
sForInternedString.parcel(this.primaryCpuAbi, dest, flags);
sForInternedString.parcel(this.secondaryCpuAbi, dest, flags);
dest.writeString(this.secondaryNativeLibraryDir);
- dest.writeString(this.seInfo);
dest.writeInt(this.uid);
dest.writeLong(this.mBooleans);
dest.writeLong(this.mBooleans2);
@@ -3307,7 +3300,6 @@
this.primaryCpuAbi = sForInternedString.unparcel(in);
this.secondaryCpuAbi = sForInternedString.unparcel(in);
this.secondaryNativeLibraryDir = in.readString();
- this.seInfo = in.readString();
this.uid = in.readInt();
this.mBooleans = in.readLong();
this.mBooleans2 = in.readLong();
@@ -3377,12 +3369,6 @@
return secondaryNativeLibraryDir;
}
- @Nullable
- @Override
- public String getSeInfo() {
- return seInfo;
- }
-
@Override
public boolean isCoreApp() {
return getBoolean(Booleans.CORE_APP);
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
index d306341..aeaff6d 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/ParsedPackage.java
@@ -103,8 +103,6 @@
ParsedPackage setRestrictUpdateHash(byte[] restrictUpdateHash);
- ParsedPackage setSeInfo(String seInfo);
-
ParsedPackage setSecondaryNativeLibraryDir(String secondaryNativeLibraryDir);
/**
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index e3dad45..84907a5 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -34,6 +34,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.SigningDetails;
import android.os.Bundle;
+import android.os.storage.StorageManager;
import android.processor.immutability.Immutable;
import android.util.ArraySet;
import android.util.Pair;
@@ -58,6 +59,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
/**
* The representation of an application on disk, as parsed from its split APKs' manifests.
@@ -111,6 +113,13 @@
String getStaticSharedLibraryName();
/**
+ * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
+ * package was installed.
+ */
+ @NonNull
+ UUID getStorageUuid();
+
+ /**
* @see ApplicationInfo#targetSdkVersion
* @see R.styleable#AndroidManifestUsesSdk_targetSdkVersion
*/
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 3c79cdf..e8d0640 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -131,6 +131,14 @@
String getSecondaryCpuAbi();
/**
+ * @see ApplicationInfo#seInfo
+ * @return The SE info for this package, which may be overridden by a system configured value,
+ * or null if the package isn't available.
+ */
+ @Nullable
+ String getSeInfo();
+
+ /**
* @see AndroidPackage#isPrivileged()
*/
boolean isPrivileged();
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index c6ce40e..e552a34 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -129,6 +129,8 @@
private final String mPrimaryCpuAbi;
@Nullable
private final String mSecondaryCpuAbi;
+ @Nullable
+ private final String mSeInfo;
private final boolean mHasSharedUser;
private final int mSharedUserAppId;
@NonNull
@@ -175,6 +177,7 @@
mPath = pkgState.getPath();
mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
+ mSeInfo = pkgState.getSeInfo();
mHasSharedUser = pkgState.hasSharedUser();
mSharedUserAppId = pkgState.getSharedUserAppId();
mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
@@ -542,7 +545,7 @@
}
@DataClass.Generated(
- time = 1661977809886L,
+ time = 1665778832625L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTime\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -641,6 +644,11 @@
}
@DataClass.Generated.Member
+ public @Nullable String getSeInfo() {
+ return mSeInfo;
+ }
+
+ @DataClass.Generated.Member
public boolean isHasSharedUser() {
return mHasSharedUser;
}
@@ -697,10 +705,10 @@
}
@DataClass.Generated(
- time = 1661977809932L,
+ time = 1665778832668L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
- inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+ inputSignatures = "private int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final int mAppId\nprivate final int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final long mLastModifiedTime\nprivate final long mLastUpdateTime\nprivate final long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final boolean mHasSharedUser\nprivate final int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final int SYSTEM\nprivate static final int EXTERNAL_STORAGE\nprivate static final int PRIVILEGED\nprivate static final int OEM\nprivate static final int VENDOR\nprivate static final int PRODUCT\nprivate static final int SYSTEM_EXT\nprivate static final int REQUIRED_FOR_SYSTEM_USER\nprivate static final int ODM\nprivate static final int FORCE_QUERYABLE_OVERRIDE\nprivate static final int HIDDEN_UNTIL_INSTALLED\nprivate static final int INSTALL_PERMISSIONS_FIXED\nprivate static final int UPDATE_AVAILABLE\nprivate static final int UPDATED_SYSTEM_APP\nprivate static final int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
index b22c038..57fbfe9 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java
@@ -22,6 +22,7 @@
import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
+import android.text.TextUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DataClass;
@@ -62,6 +63,9 @@
@Nullable
private String overrideSeInfo;
+ @NonNull
+ private String seInfo;
+
// TODO: Remove in favor of finer grained change notification
@NonNull
private final PackageSetting mPackageSetting;
@@ -138,6 +142,7 @@
this.apkInUpdatedApex = other.apkInUpdatedApex;
this.lastPackageUsageTimeInMills = other.lastPackageUsageTimeInMills;
this.overrideSeInfo = other.overrideSeInfo;
+ this.seInfo = other.seInfo;
mPackageSetting.onChanged();
}
@@ -206,6 +211,13 @@
return this;
}
+ @NonNull
+ public PackageStateUnserialized setSeInfo(@NonNull String value) {
+ seInfo = TextUtils.safeIntern(value);
+ mPackageSetting.onChanged();
+ return this;
+ }
+
// Code below generated by codegen v1.0.23.
@@ -271,15 +283,20 @@
}
@DataClass.Generated.Member
+ public @NonNull String getSeInfo() {
+ return seInfo;
+ }
+
+ @DataClass.Generated.Member
public @NonNull PackageSetting getPackageSetting() {
return mPackageSetting;
}
@DataClass.Generated(
- time = 1661373697219L,
+ time = 1666291743725L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateUnserialized.java",
- inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
+ inputSignatures = "private boolean hiddenUntilInstalled\nprivate @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibraryWrapper> usesLibraryInfos\nprivate @android.annotation.NonNull java.util.List<java.lang.String> usesLibraryFiles\nprivate boolean updatedSystemApp\nprivate boolean apkInApex\nprivate boolean apkInUpdatedApex\nprivate volatile @android.annotation.NonNull long[] lastPackageUsageTimeInMills\nprivate @android.annotation.Nullable java.lang.String overrideSeInfo\nprivate @android.annotation.NonNull java.lang.String seInfo\nprivate final @android.annotation.NonNull com.android.server.pm.PackageSetting mPackageSetting\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryInfo(com.android.server.pm.pkg.SharedLibraryWrapper)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized addUsesLibraryFile(java.lang.String)\nprivate long[] lazyInitLastPackageUsageTimeInMills()\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(int,long)\npublic long getLatestPackageUseTimeInMills()\npublic long getLatestForegroundPackageUseTimeInMills()\npublic void updateFrom(com.android.server.pm.pkg.PackageStateUnserialized)\npublic @android.annotation.NonNull java.util.List<android.content.pm.SharedLibraryInfo> getNonNativeUsesLibraryInfos()\npublic com.android.server.pm.pkg.PackageStateUnserialized setHiddenUntilInstalled(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryInfos(java.util.List<android.content.pm.SharedLibraryInfo>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUsesLibraryFiles(java.util.List<java.lang.String>)\npublic com.android.server.pm.pkg.PackageStateUnserialized setUpdatedSystemApp(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setApkInUpdatedApex(boolean)\npublic com.android.server.pm.pkg.PackageStateUnserialized setLastPackageUsageTimeInMills(long)\npublic com.android.server.pm.pkg.PackageStateUnserialized setOverrideSeInfo(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized setSeInfo(java.lang.String)\nclass PackageStateUnserialized extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genConstructor=false, genBuilder=false)")
@Deprecated
private void __metadata() {}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 3aa333a..e9c93ee 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4208,11 +4208,13 @@
wakeUpFromWakeKey(event);
}
- if ((result & ACTION_PASS_TO_USER) != 0) {
+ if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
+ && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
// If the key event is targeted to a specific display, then the user is interacting with
- // that display. Therefore, give focus to the display that the user is interacting with.
- if (!mPerDisplayFocusEnabled
- && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+ // that display. Therefore, give focus to the display that the user is interacting with,
+ // unless that display maintains its own focus.
+ Display display = mDisplayManager.getDisplay(displayId);
+ if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
// An event is targeting a non-focused display. Move the display to top so that
// it can become the focused display to interact with the user.
// This should be done asynchronously, once the focus logic is fully moved to input
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9281f4b..1ea0988 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2204,6 +2204,15 @@
if (sQuiescent) {
mDirty |= DIRTY_QUIESCENT;
}
+ PowerGroup defaultGroup = mPowerGroups.get(Display.DEFAULT_DISPLAY_GROUP);
+ if (defaultGroup.getWakefulnessLocked() == WAKEFULNESS_DOZING) {
+ // Workaround for b/187231320 where the AOD can get stuck in a "half on /
+ // half off" state when a non-default-group VirtualDisplay causes the global
+ // wakefulness to change to awake, even though the default display is
+ // dozing. We set sandman summoned to restart dreaming to get it unstuck.
+ // TODO(b/255688811) - fix this so that AOD never gets interrupted at all.
+ defaultGroup.setSandmanSummonedLocked(true);
+ }
break;
case WAKEFULNESS_ASLEEP:
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644..f17e5e7 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@
public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
/**
+ * Creates a sensor that is registered at runtime by the system with the sensor service.
+ *
+ * The runtime sensors created here are different from the
+ * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+ * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+ * sensors that belong to an external (virtual) device.
+ *
+ * @param deviceId The identifier of the device this sensor is associated with.
+ * @param type The generic type of the sensor.
+ * @param name The name of the sensor.
+ * @param vendor The vendor string of the sensor.
+ * @param callback The callback to get notified when the sensor listeners have changed.
+ * @return The sensor handle.
+ */
+ public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+ /**
+ * Unregisters the sensor with the given handle from the framework.
+ */
+ public abstract void removeRuntimeSensor(int handle);
+
+ /**
+ * Sends an event for the runtime sensor with the given handle to the framework.
+ *
+ * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+ *
+ * @param handle The sensor handle.
+ * @param type The type of the sensor.
+ * @param timestampNanos When the event occurred.
+ * @param values The values of the event.
+ * @return Whether the event injection was successful.
+ */
+ public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values);
+
+ /**
* Listener for proximity sensor state changes.
*/
public interface ProximityActiveListener {
@@ -52,4 +89,17 @@
*/
void onProximityActive(boolean isActive);
}
+
+ /**
+ * Callback for runtime sensor state changes. Only relevant to sensors created via
+ * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+ * not covered.
+ */
+ public interface RuntimeSensorStateChangeCallback {
+ /**
+ * Invoked when the listeners of the runtime sensor have changed.
+ */
+ void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros);
+ }
}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52..d8e3bdd 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@
import com.android.server.SystemService;
import com.android.server.utils.TimingsTraceAndSlog;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -40,6 +42,8 @@
private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
new ArrayMap<>();
@GuardedBy("mLock")
+ private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+ @GuardedBy("mLock")
private Future<?> mSensorServiceStart;
@GuardedBy("mLock")
private long mPtr;
@@ -51,6 +55,12 @@
private static native void registerProximityActiveListenerNative(long ptr);
private static native void unregisterProximityActiveListenerNative(long ptr);
+ private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+ String name, String vendor,
+ SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+ private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+ private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+ long timestampNanos, float[] values);
public SensorService(Context ctx) {
super(ctx);
@@ -85,6 +95,38 @@
class LocalService extends SensorManagerInternal {
@Override
+ public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+ synchronized (mLock) {
+ int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+ callback);
+ mRuntimeSensorHandles.add(handle);
+ return handle;
+ }
+ }
+
+ @Override
+ public void removeRuntimeSensor(int handle) {
+ synchronized (mLock) {
+ if (mRuntimeSensorHandles.contains(handle)) {
+ mRuntimeSensorHandles.remove(handle);
+ unregisterRuntimeSensorNative(mPtr, handle);
+ }
+ }
+ }
+
+ @Override
+ public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values) {
+ synchronized (mLock) {
+ if (!mRuntimeSensorHandles.contains(handle)) {
+ return false;
+ }
+ return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+ }
+ }
+
+ @Override
public void addProximityActiveListener(@NonNull Executor executor,
@NonNull ProximityActiveListener listener) {
Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
index 0b1f6b9..f971db9 100644
--- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java
+++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java
@@ -107,6 +107,7 @@
// Trust state
private boolean mTrusted;
private boolean mWaitingForTrustableDowngrade = false;
+ private boolean mWithinSecurityLockdownWindow = false;
private boolean mTrustable;
private CharSequence mMessage;
private boolean mDisplayTrustGrantedMessage;
@@ -160,6 +161,7 @@
mDisplayTrustGrantedMessage = (flags & FLAG_GRANT_TRUST_DISPLAY_MESSAGE) != 0;
if ((flags & FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0) {
mWaitingForTrustableDowngrade = true;
+ setSecurityWindowTimer();
} else {
mWaitingForTrustableDowngrade = false;
}
@@ -452,6 +454,9 @@
if (mBound) {
scheduleRestart();
}
+ if (mWithinSecurityLockdownWindow) {
+ mTrustManagerService.lockUser(mUserId);
+ }
// mTrustDisabledByDpm maintains state
}
};
@@ -673,6 +678,22 @@
}
}
+ private void setSecurityWindowTimer() {
+ mWithinSecurityLockdownWindow = true;
+ long expiration = SystemClock.elapsedRealtime() + (15 * 1000); // timer for 15 seconds
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ expiration,
+ TAG,
+ new AlarmManager.OnAlarmListener() {
+ @Override
+ public void onAlarm() {
+ mWithinSecurityLockdownWindow = false;
+ }
+ },
+ Handler.getMain());
+ }
+
public boolean isManagingTrust() {
return mManagingTrust && !mTrustDisabledByDpm;
}
@@ -691,7 +712,6 @@
public void destroy() {
mHandler.removeMessages(MSG_RESTART_TIMEOUT);
-
if (!mBound) {
return;
}
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 770cb72..4772bbf 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -36,8 +36,11 @@
*/
public class EventLogger {
+ /** Prefix for the title added at the beginning of a {@link #dump(PrintWriter)} operation */
+ private static final String DUMP_TITLE_PREFIX = "Events log: ";
+
/** Identifies the source of events. */
- private final String mTag;
+ @Nullable private final String mTag;
/** Stores the events using a ring buffer. */
private final ArrayDeque<Event> mEvents;
@@ -55,7 +58,7 @@
* @param size the maximum number of events to keep in log
* @param tag the string displayed before the recorded log
*/
- public EventLogger(int size, String tag) {
+ public EventLogger(int size, @Nullable String tag) {
mEvents = new ArrayDeque<>(size);
mMemSize = size;
mTag = tag;
@@ -64,10 +67,10 @@
/** Enqueues {@code event} to be logged. */
public synchronized void enqueue(Event event) {
if (mEvents.size() >= mMemSize) {
- mEvents.removeLast();
+ mEvents.removeFirst();
}
- mEvents.addFirst(event);
+ mEvents.addLast(event);
}
/**
@@ -91,13 +94,19 @@
dump(pw, "" /* prefix */);
}
+ protected String getDumpTitle() {
+ if (mTag == null) {
+ return DUMP_TITLE_PREFIX;
+ }
+ return DUMP_TITLE_PREFIX + mTag;
+ }
+
/** Dumps events using {@link PrintWriter} with a certain indent. */
public synchronized void dump(PrintWriter pw, String indent) {
- pw.println(indent + "Events log: " + mTag);
+ pw.println(getDumpTitle());
- String childrenIndention = indent + " ";
for (Event evt : mEvents) {
- pw.println(childrenIndention + evt.toString());
+ pw.println(indent + evt.toString());
}
}
diff --git a/services/core/java/com/android/server/utils/Slogf.java b/services/core/java/com/android/server/utils/Slogf.java
index e88ac63..6efbd89 100644
--- a/services/core/java/com/android/server/utils/Slogf.java
+++ b/services/core/java/com/android/server/utils/Slogf.java
@@ -162,7 +162,7 @@
}
/**
- * Logs a {@link Log.VEBOSE} message with an exception
+ * Logs a {@link Log.VEBOSE} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#VERBOSE} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -170,10 +170,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void v(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void v(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.VERBOSE)) return;
- v(tag, getMessage(format, args), exception);
+ v(tag, getMessage(format, args), throwable);
}
/**
@@ -192,7 +192,7 @@
}
/**
- * Logs a {@link Log.DEBUG} message with an exception
+ * Logs a {@link Log.DEBUG} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#DEBUG} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -200,10 +200,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void d(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void d(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.DEBUG)) return;
- d(tag, getMessage(format, args), exception);
+ d(tag, getMessage(format, args), throwable);
}
/**
@@ -222,7 +222,7 @@
}
/**
- * Logs a {@link Log.INFO} message with an exception
+ * Logs a {@link Log.INFO} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#INFO} logging
* is enabled for the given {@code tag}, but the compiler will still create an intermediate
@@ -230,10 +230,10 @@
* you're calling this method in a critical path, make sure to explicitly do the check before
* calling it.
*/
- public static void i(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void i(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.INFO)) return;
- i(tag, getMessage(format, args), exception);
+ i(tag, getMessage(format, args), throwable);
}
/**
@@ -252,7 +252,7 @@
}
/**
- * Logs a {@link Log.WARN} message with an exception
+ * Logs a {@link Log.WARN} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#WARN} logging is
* enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -260,10 +260,10 @@
* calling this method in a critical path, make sure to explicitly do the check before calling
* it.
*/
- public static void w(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void w(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.WARN)) return;
- w(tag, getMessage(format, args), exception);
+ w(tag, getMessage(format, args), throwable);
}
/**
@@ -282,7 +282,7 @@
}
/**
- * Logs a {@link Log.ERROR} message with an exception
+ * Logs a {@link Log.ERROR} message with a throwable
*
* <p><strong>Note: </strong>the message will only be formatted if {@link Log#ERROR} logging is
* enabled for the given {@code tag}, but the compiler will still create an intermediate array
@@ -290,10 +290,10 @@
* calling this method in a critical path, make sure to explicitly do the check before calling
* it.
*/
- public static void e(String tag, Exception exception, String format, @Nullable Object... args) {
+ public static void e(String tag, Throwable throwable, String format, @Nullable Object... args) {
if (!isLoggable(tag, Log.ERROR)) return;
- e(tag, getMessage(format, args), exception);
+ e(tag, getMessage(format, args), throwable);
}
/**
@@ -304,11 +304,11 @@
}
/**
- * Logs a {@code wtf} message with an exception.
+ * Logs a {@code wtf} message with a throwable.
*/
- public static void wtf(String tag, Exception exception, String format,
+ public static void wtf(String tag, Throwable throwable, String format,
@Nullable Object... args) {
- wtf(tag, getMessage(format, args), exception);
+ wtf(tag, getMessage(format, args), throwable);
}
private static String getMessage(String format, @Nullable Object... args) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f74956b..5d08461 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1559,8 +1559,9 @@
try {
mReply.sendResult(null);
} catch (RemoteException e) {
- Binder.restoreCallingIdentity(ident);
Slog.d(TAG, "failed to send callback!", e);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
t.traceEnd();
mReply = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 530fa4d..12424c0 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3317,9 +3317,17 @@
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
resultTo.getUriPermissionsLocked());
}
- if (mForceSendResultForMediaProjection) {
- resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
- resultData, resultGrants, true /* forceSendForMediaProjection */);
+ if (mForceSendResultForMediaProjection || resultTo.isState(RESUMED)) {
+ // Sending the result to the resultTo activity asynchronously to prevent the
+ // resultTo activity getting results before this Activity paused.
+ final ActivityRecord resultToActivity = resultTo;
+ mAtmService.mH.post(() -> {
+ synchronized (mAtmService.mGlobalLock) {
+ resultToActivity.sendResult(this.getUid(), resultWho, requestCode,
+ resultCode, resultData, resultGrants,
+ mForceSendResultForMediaProjection);
+ }
+ });
} else {
resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
}
@@ -4630,7 +4638,7 @@
false /* forceSendForMediaProjection */);
}
- private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+ void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
if (callingUid > 0) {
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index d7c5e93..719f72c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -399,8 +399,11 @@
* @return The intercepting intent if needed.
*/
private Intent interceptWithConfirmCredentialsIfNeeded(ActivityInfo aInfo, int userId) {
+ if (!mService.mAmInternal.shouldConfirmCredentials(userId)) {
+ return null;
+ }
if ((aInfo.flags & ActivityInfo.FLAG_SHOW_WHEN_LOCKED) != 0
- || !mService.mAmInternal.shouldConfirmCredentials(userId)) {
+ && (mUserManager.isUserUnlocked(userId) || aInfo.directBootAware)) {
return null;
}
final IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5938e7f..0b16a4d 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -78,7 +78,6 @@
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -2777,11 +2776,6 @@
errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity;
break;
}
- case EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT: {
- errMsg = "Cannot embed activity across TaskFragments for result, resultTo: "
- + mStartActivity.resultTo;
- break;
- }
default:
errMsg = "Unhandled embed result:" + result;
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 14d6d7b..798e739 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -241,6 +241,7 @@
// We have another Activity in the same currentTask to go to
backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
removedWindowContainer = currentActivity;
+ prevTask = prevActivity.getTask();
} else if (currentTask.returnsToHomeRootTask()) {
// Our Task should bring back to home
removedWindowContainer = currentTask;
@@ -608,26 +609,23 @@
// reset leash after animation finished.
leashes.add(screenshotSurface);
}
- } else if (prevTask != null) {
- prevActivity = prevTask.getTopNonFinishingActivity();
- if (prevActivity != null) {
- // Make previous task show from behind by marking its top activity as visible
- // and launch-behind to bump its visibility for the duration of the back gesture.
- setLaunchBehind(prevActivity);
+ } else if (prevTask != null && prevActivity != null) {
+ // Make previous task show from behind by marking its top activity as visible
+ // and launch-behind to bump its visibility for the duration of the back gesture.
+ setLaunchBehind(prevActivity);
- final SurfaceControl leash = prevActivity.makeAnimationLeash()
- .setName("BackPreview Leash for " + prevActivity)
- .setHidden(false)
- .build();
- prevActivity.reparentSurfaceControl(startedTransaction, leash);
- behindAppTarget = createRemoteAnimationTargetLocked(
- prevTask, leash, MODE_OPENING);
+ final SurfaceControl leash = prevActivity.makeAnimationLeash()
+ .setName("BackPreview Leash for " + prevActivity)
+ .setHidden(false)
+ .build();
+ prevActivity.reparentSurfaceControl(startedTransaction, leash);
+ behindAppTarget = createRemoteAnimationTargetLocked(
+ prevTask, leash, MODE_OPENING);
- // reset leash after animation finished.
- leashes.add(leash);
- prevActivity.reparentSurfaceControl(finishedTransaction,
- prevActivity.getParentSurfaceControl());
- }
+ // reset leash after animation finished.
+ leashes.add(leash);
+ prevActivity.reparentSurfaceControl(finishedTransaction,
+ prevActivity.getParentSurfaceControl());
}
if (mShowWallpaper) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0119e4d..d1122e1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -207,6 +207,7 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
import android.view.ISystemGestureExclusionListener;
@@ -391,6 +392,10 @@
mPrivacyIndicatorBoundsCache = new
RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached);
+ DisplayShape mInitialDisplayShape;
+ private final RotationCache<DisplayShape, DisplayShape> mDisplayShapeCache =
+ new RotationCache<>(this::calculateDisplayShapeForRotationUncached);
+
/**
* Overridden display size. Initialized with {@link #mInitialDisplayWidth}
* and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size".
@@ -1095,7 +1100,8 @@
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
- calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
+ calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation),
+ calculateDisplayShapeForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
@@ -1969,8 +1975,9 @@
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
final PrivacyIndicatorBounds indicatorBounds =
calculatePrivacyIndicatorBoundsForRotation(rotation);
+ final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(), info,
- cutout, roundedCorners, indicatorBounds);
+ cutout, roundedCorners, indicatorBounds, displayShape);
token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration);
}
@@ -2178,6 +2185,7 @@
// Update application display metrics.
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation);
final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation);
+ final DisplayShape displayShape = calculateDisplayShapeForRotation(rotation);
final Rect appFrame = mDisplayPolicy.getDecorInsetsInfo(rotation, dw, dh).mNonDecorFrame;
mDisplayInfo.rotation = rotation;
@@ -2194,6 +2202,7 @@
}
mDisplayInfo.displayCutout = displayCutout.isEmpty() ? null : displayCutout;
mDisplayInfo.roundedCorners = roundedCorners;
+ mDisplayInfo.displayShape = displayShape;
mDisplayInfo.getAppMetrics(mDisplayMetrics);
if (mDisplayScalingDisabled) {
mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED;
@@ -2280,6 +2289,23 @@
return bounds.rotate(rotation);
}
+ DisplayShape calculateDisplayShapeForRotation(int rotation) {
+ return mDisplayShapeCache.getOrCompute(mInitialDisplayShape, rotation);
+ }
+
+ private DisplayShape calculateDisplayShapeForRotationUncached(
+ DisplayShape displayShape, int rotation) {
+ if (displayShape == null) {
+ return DisplayShape.NONE;
+ }
+
+ if (rotation == ROTATION_0) {
+ return displayShape;
+ }
+
+ return displayShape.setRotation(rotation);
+ }
+
/**
* Compute display info and configuration according to the given rotation without changing
* current display.
@@ -2781,7 +2807,8 @@
return displayFrames.update(rotation, w, h,
calculateDisplayCutoutForRotation(rotation),
calculateRoundedCornersForRotation(rotation),
- calculatePrivacyIndicatorBoundsForRotation(rotation));
+ calculatePrivacyIndicatorBoundsForRotation(rotation),
+ calculateDisplayShapeForRotation(rotation));
}
@Override
@@ -2821,6 +2848,7 @@
mInitialRoundedCorners = mDisplayInfo.roundedCorners;
mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4],
mDisplayInfo.rotation);
+ mInitialDisplayShape = mDisplayInfo.displayShape;
final Display.Mode maxDisplayMode =
DisplayUtils.getMaximumResolutionDisplayMode(mDisplayInfo.supportedModes);
mPhysicalDisplaySize = new Point(
@@ -2848,6 +2876,7 @@
? DisplayCutout.NO_CUTOUT : mDisplayInfo.displayCutout;
final String newUniqueId = mDisplayInfo.uniqueId;
final RoundedCorners newRoundedCorners = mDisplayInfo.roundedCorners;
+ final DisplayShape newDisplayShape = mDisplayInfo.displayShape;
final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth
|| mInitialDisplayHeight != newHeight
@@ -2855,7 +2884,8 @@
|| mInitialPhysicalXDpi != newXDpi
|| mInitialPhysicalYDpi != newYDpi
|| !Objects.equals(mInitialDisplayCutout, newCutout)
- || !Objects.equals(mInitialRoundedCorners, newRoundedCorners);
+ || !Objects.equals(mInitialRoundedCorners, newRoundedCorners)
+ || !Objects.equals(mInitialDisplayShape, newDisplayShape);
final boolean physicalDisplayChanged = !newUniqueId.equals(mCurrentUniqueDisplayId);
if (displayMetricsChanged || physicalDisplayChanged) {
@@ -2893,6 +2923,7 @@
mInitialPhysicalYDpi = newYDpi;
mInitialDisplayCutout = newCutout;
mInitialRoundedCorners = newRoundedCorners;
+ mInitialDisplayShape = newDisplayShape;
mCurrentUniqueDisplayId = newUniqueId;
reconfigureDisplayLocked();
@@ -3380,7 +3411,7 @@
}
}
mWmService.mLatencyTracker.onActionStart(ACTION_ROTATE_SCREEN);
- controller.mTransitionMetricsReporter.associate(t,
+ controller.mTransitionMetricsReporter.associate(t.getToken(),
startTime -> mWmService.mLatencyTracker.onActionEnd(ACTION_ROTATE_SCREEN));
startAsyncRotation(false /* shouldDebounce */);
}
@@ -3683,7 +3714,7 @@
* @return The focused window or null if there isn't any or no need to seek.
*/
WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
- return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+ return (hasOwnFocus() || topFocusedDisplayId == INVALID_DISPLAY)
? findFocusedWindow() : null;
}
@@ -6315,6 +6346,14 @@
}
/**
+ * @return whether this display maintains its own focus and touch mode.
+ */
+ boolean hasOwnFocus() {
+ return mWmService.mPerDisplayFocusEnabled
+ || (mDisplayInfo.flags & Display.FLAG_OWN_FOCUS) != 0;
+ }
+
+ /**
* @return whether the keyguard is occluded on this display
*/
boolean isKeyguardOccluded() {
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index 33641f7..e984456 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -26,6 +26,7 @@
import android.util.proto.ProtoOutputStream;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
@@ -56,10 +57,11 @@
public int mRotation;
public DisplayFrames(InsetsState insetsState, DisplayInfo info, DisplayCutout cutout,
- RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds) {
+ RoundedCorners roundedCorners, PrivacyIndicatorBounds indicatorBounds,
+ DisplayShape displayShape) {
mInsetsState = insetsState;
update(info.rotation, info.logicalWidth, info.logicalHeight, cutout, roundedCorners,
- indicatorBounds);
+ indicatorBounds, displayShape);
}
DisplayFrames() {
@@ -73,7 +75,8 @@
*/
public boolean update(int rotation, int w, int h, @NonNull DisplayCutout displayCutout,
@NonNull RoundedCorners roundedCorners,
- @NonNull PrivacyIndicatorBounds indicatorBounds) {
+ @NonNull PrivacyIndicatorBounds indicatorBounds,
+ @NonNull DisplayShape displayShape) {
final InsetsState state = mInsetsState;
final Rect safe = mDisplayCutoutSafe;
if (mRotation == rotation && mWidth == w && mHeight == h
@@ -91,6 +94,7 @@
state.setDisplayCutout(displayCutout);
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
+ state.setDisplayShape(displayShape);
state.getDisplayCutoutSafe(safe);
if (safe.left > unrestricted.left) {
state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7860b15..3e1105b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -270,7 +270,7 @@
InputConfigAdapter.getMask());
final boolean focusable = w.canReceiveKeys()
- && (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
+ && (mDisplayContent.hasOwnFocus() || mDisplayContent.isOnTop());
inputWindowHandle.setFocusable(focusable);
final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 2dbccae..bb4c482 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -87,10 +87,6 @@
private final LetterboxConfiguration mLetterboxConfiguration;
private final ActivityRecord mActivityRecord;
- // Taskbar expanded height. Used to determine whether to crop an app window to display rounded
- // corners above the taskbar.
- private final float mExpandedTaskBarHeight;
-
private boolean mShowWallpaperForLetterboxBackground;
@Nullable
@@ -102,8 +98,6 @@
// is created in its constructor. It shouldn't be used in this constructor but it's safe
// to use it after since controller is only used in ActivityRecord.
mActivityRecord = activityRecord;
- mExpandedTaskBarHeight =
- getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/** Cleans up {@link Letterbox} if it exists.*/
@@ -285,14 +279,17 @@
}
float getSplitScreenAspectRatio() {
+ // Getting the same aspect ratio that apps get in split screen.
+ final DisplayContent displayContent = mActivityRecord.getDisplayContent();
+ if (displayContent == null) {
+ return getDefaultMinAspectRatioForUnresizableApps();
+ }
int dividerWindowWidth =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_thickness);
int dividerInsets =
getResources().getDimensionPixelSize(R.dimen.docked_stack_divider_insets);
int dividerSize = dividerWindowWidth - dividerInsets * 2;
-
- // Getting the same aspect ratio that apps get in split screen.
- Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+ final Rect bounds = new Rect(displayContent.getBounds());
if (bounds.width() >= bounds.height()) {
bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
bounds.right = bounds.centerX();
@@ -555,7 +552,6 @@
final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
return taskbarInsetsSource != null
- && taskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight
&& taskbarInsetsSource.isVisible();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 91cb037..d3a3cf5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -172,13 +172,6 @@
* indicate that an Activity can't be embedded because the Activity is started on a new task.
*/
static final int EMBEDDING_DISALLOWED_NEW_TASK = 3;
- /**
- * An embedding check result of
- * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}:
- * indicate that an Activity can't be embedded because the Activity is started on a new
- * TaskFragment, e.g. start an Activity on a new TaskFragment for result.
- */
- static final int EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT = 4;
/**
* Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or
@@ -189,7 +182,6 @@
EMBEDDING_DISALLOWED_UNTRUSTED_HOST,
EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
EMBEDDING_DISALLOWED_NEW_TASK,
- EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
})
@interface EmbeddingCheckResult {}
@@ -616,14 +608,6 @@
return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
}
- // Cannot embed activity across TaskFragments for activity result.
- // If the activity that started for result is finishing, it's likely that this start mode
- // is used to place an activity in the same task. Since the finishing activity won't be
- // able to get the results, so it's OK to embed in a different TaskFragment.
- if (a.resultTo != null && !a.resultTo.finishing && a.resultTo.getTaskFragment() != this) {
- return EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
- }
-
return EMBEDDING_ALLOWED;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b277804..9cb13e4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -91,6 +91,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -100,7 +101,7 @@
* Represents a logical transition.
* @see TransitionController
*/
-class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListener {
+class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
@@ -151,6 +152,7 @@
private @TransitionFlags int mFlags;
private final TransitionController mController;
private final BLASTSyncEngine mSyncEngine;
+ private final Token mToken;
private RemoteTransition mRemoteTransition = null;
/** Only use for clean-up after binder death! */
@@ -213,10 +215,26 @@
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
+ mToken = new Token(this);
controller.mTransitionTracer.logState(this);
}
+ @Nullable
+ static Transition fromBinder(@NonNull IBinder token) {
+ try {
+ return ((Token) token).mTransition.get();
+ } catch (ClassCastException e) {
+ Slog.w(TAG, "Invalid transition token: " + token, e);
+ return null;
+ }
+ }
+
+ @NonNull
+ IBinder getToken() {
+ return mToken;
+ }
+
void addFlag(int flag) {
mFlags |= flag;
}
@@ -726,6 +744,11 @@
Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
}
+ // Close the transactions now. They were originally copied to Shell in case we needed to
+ // apply them due to a remote failure. Since we don't need to apply them anymore, free them
+ // immediately.
+ if (mStartTransaction != null) mStartTransaction.close();
+ if (mFinishTransaction != null) mFinishTransaction.close();
mStartTransaction = mFinishTransaction = null;
if (mState < STATE_PLAYING) {
throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId);
@@ -867,6 +890,7 @@
mController.mAtm.mWindowManager.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
+ cleanUpInternal();
}
void abort() {
@@ -909,6 +933,7 @@
dc.getPendingTransaction().merge(transaction);
mSyncId = -1;
mOverrideOptions = null;
+ cleanUpInternal();
return;
}
@@ -1026,7 +1051,9 @@
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
mController.getTransitionPlayer().onTransitionReady(
- this, info, transaction, mFinishTransaction);
+ mToken, info, transaction, mFinishTransaction);
+ // Since we created root-leash but no longer reference it from core, release it now
+ info.releaseAnimSurfaces();
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
System.identityHashCode(this));
@@ -1059,7 +1086,17 @@
if (mFinishTransaction != null) {
mFinishTransaction.apply();
}
- mController.finishTransition(this);
+ mController.finishTransition(mToken);
+ }
+
+ private void cleanUpInternal() {
+ // Clean-up any native references.
+ for (int i = 0; i < mChanges.size(); ++i) {
+ final ChangeInfo ci = mChanges.valueAt(i);
+ if (ci.mSnapshot != null) {
+ ci.mSnapshot.release();
+ }
+ }
}
/** @see RecentsAnimationController#attachNavigationBarToApp */
@@ -1815,10 +1852,6 @@
return isCollecting() && mSyncId >= 0;
}
- static Transition fromBinder(IBinder binder) {
- return (Transition) binder;
- }
-
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
@@ -2325,4 +2358,18 @@
}
}
}
+
+ private static class Token extends Binder {
+ final WeakReference<Transition> mTransition;
+
+ Token(Transition transition) {
+ mTransition = new WeakReference<>(transition);
+ }
+
+ @Override
+ public String toString() {
+ return "Token{" + Integer.toHexString(System.identityHashCode(this)) + " "
+ + mTransition.get() + "}";
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 25df511..99527b1 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -458,8 +458,9 @@
info = new ActivityManager.RunningTaskInfo();
startTask.fillTaskInfo(info);
}
- mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo(
- transition.mType, info, remoteTransition, displayChange));
+ mTransitionPlayer.requestStartTransition(transition.getToken(),
+ new TransitionRequestInfo(transition.mType, info, remoteTransition,
+ displayChange));
transition.setRemoteTransition(remoteTransition);
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting transition", e);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 6032f87..f6f825f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3847,6 +3847,11 @@
|| displayContent.isInTouchMode() == inTouch)) {
return;
}
+ final boolean displayHasOwnTouchMode =
+ displayContent != null && displayContent.hasOwnFocus();
+ if (displayHasOwnTouchMode && displayContent.isInTouchMode() == inTouch) {
+ return;
+ }
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final boolean hasPermission =
@@ -3855,17 +3860,17 @@
/* printlog= */ false);
final long token = Binder.clearCallingIdentity();
try {
- // If perDisplayFocusEnabled is set, then just update the display pointed by
- // displayId
- if (perDisplayFocusEnabled) {
+ // If perDisplayFocusEnabled is set or the display maintains its own touch mode,
+ // then just update the display pointed by displayId
+ if (perDisplayFocusEnabled || displayHasOwnTouchMode) {
if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission, displayId)) {
displayContent.setInTouchMode(inTouch);
}
- } else { // Otherwise update all displays
+ } else { // Otherwise update all displays that do not maintain their own touch mode
final int displayCount = mRoot.mChildren.size();
for (int i = 0; i < displayCount; ++i) {
DisplayContent dc = mRoot.mChildren.get(i);
- if (dc.isInTouchMode() == inTouch) {
+ if (dc.isInTouchMode() == inTouch || dc.hasOwnFocus()) {
continue;
}
if (mInputManager.setInTouchMode(inTouch, pid, uid, hasPermission,
@@ -8935,14 +8940,14 @@
}
@Override
- public List<DisplayInfo> getPossibleDisplayInfo(int displayId, String packageName) {
+ public List<DisplayInfo> getPossibleDisplayInfo(int displayId) {
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- if (packageName == null || !isRecentsComponent(packageName, callingUid)) {
- Slog.e(TAG, "Unable to verify uid for package " + packageName
- + " for getPossibleMaximumWindowMetrics");
+ if (!mAtmService.isCallerRecents(callingUid)) {
+ Slog.e(TAG, "Unable to verify uid for getPossibleDisplayInfo"
+ + " on uid " + callingUid);
return new ArrayList<>();
}
@@ -8960,31 +8965,6 @@
return mPossibleDisplayInfoMapper.getPossibleDisplayInfos(displayId);
}
- /**
- * Returns {@code true} when the calling package is the recents component.
- */
- boolean isRecentsComponent(@NonNull String callingPackageName, int callingUid) {
- String recentsPackage;
- try {
- String recentsComponent = mContext.getResources().getString(
- R.string.config_recentsComponentName);
- if (recentsComponent == null) {
- return false;
- }
- recentsPackage = ComponentName.unflattenFromString(recentsComponent).getPackageName();
- } catch (Resources.NotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- try {
- return callingUid == mContext.getPackageManager().getPackageUid(callingPackageName, 0)
- && callingPackageName.equals(recentsPackage);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Unable to verify if recents component", e);
- return false;
- }
- }
-
void grantEmbeddedWindowFocus(Session session, IBinder focusToken, boolean grantFocus) {
synchronized (mGlobalLock) {
final EmbeddedWindowController.EmbeddedWindow embeddedWindow =
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4c35178..aa1cf56 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -306,7 +306,7 @@
nextTransition.setAllReady();
}
});
- return nextTransition;
+ return nextTransition.getToken();
}
transition = mTransitionController.createTransition(type);
}
@@ -315,7 +315,7 @@
if (needsSetReady) {
transition.setAllReady();
}
- return transition;
+ return transition.getToken();
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfb..10d8b42 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <jni.h>
#include <sensorservice/SensorService.h>
+#include <string.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -30,10 +31,14 @@
#define PROXIMITY_ACTIVE_CLASS \
"com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+ "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
class NativeSensorService {
public:
@@ -41,6 +46,11 @@
void registerProximityActiveListener();
void unregisterProximityActiveListener();
+ jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+ jobject callback);
+ void unregisterRuntimeSensor(jint handle);
+ jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+ jfloatArray values);
private:
sp<SensorService> mService;
@@ -56,6 +66,18 @@
jobject mListener;
};
sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+ class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+ public:
+ RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+ ~RuntimeSensorCallbackDelegate();
+
+ void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override;
+
+ private:
+ jobject mCallback;
+ };
};
NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@
mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
}
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+ jstring vendor, jobject callback) {
+ if (mService == nullptr) {
+ ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+ return -1;
+ }
+
+ sensor_t sensor{
+ .name = env->GetStringUTFChars(name, 0),
+ .vendor = env->GetStringUTFChars(vendor, 0),
+ .version = sizeof(sensor_t),
+ .type = type,
+ };
+
+ sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+ new RuntimeSensorCallbackDelegate(env, callback));
+ return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+ if (mService == nullptr) {
+ ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+ return;
+ }
+
+ mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ if (mService == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+ return false;
+ }
+ if (values == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+ return false;
+ }
+
+ sensors_event_t event{
+ .version = sizeof(sensors_event_t),
+ .timestamp = timestamp,
+ .sensor = handle,
+ .type = type,
+ };
+
+ int valuesLength = env->GetArrayLength(values);
+ jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+ switch (type) {
+ case SENSOR_TYPE_ACCELEROMETER:
+ case SENSOR_TYPE_MAGNETIC_FIELD:
+ case SENSOR_TYPE_ORIENTATION:
+ case SENSOR_TYPE_GYROSCOPE:
+ case SENSOR_TYPE_GRAVITY:
+ case SENSOR_TYPE_LINEAR_ACCELERATION: {
+ if (valuesLength != 3) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.acceleration.x = sensorValues[0];
+ event.acceleration.y = sensorValues[1];
+ event.acceleration.z = sensorValues[2];
+ break;
+ }
+ case SENSOR_TYPE_DEVICE_ORIENTATION:
+ case SENSOR_TYPE_LIGHT:
+ case SENSOR_TYPE_PRESSURE:
+ case SENSOR_TYPE_TEMPERATURE:
+ case SENSOR_TYPE_PROXIMITY:
+ case SENSOR_TYPE_RELATIVE_HUMIDITY:
+ case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+ case SENSOR_TYPE_SIGNIFICANT_MOTION:
+ case SENSOR_TYPE_STEP_DETECTOR:
+ case SENSOR_TYPE_TILT_DETECTOR:
+ case SENSOR_TYPE_WAKE_GESTURE:
+ case SENSOR_TYPE_GLANCE_GESTURE:
+ case SENSOR_TYPE_PICK_UP_GESTURE:
+ case SENSOR_TYPE_WRIST_TILT_GESTURE:
+ case SENSOR_TYPE_STATIONARY_DETECT:
+ case SENSOR_TYPE_MOTION_DETECT:
+ case SENSOR_TYPE_HEART_BEAT:
+ case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+ if (valuesLength != 1) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.data[0] = sensorValues[0];
+ break;
+ }
+ default: {
+ if (valuesLength > 16) {
+ ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+ return false;
+ }
+ memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+ }
+ }
+
+ status_t err = mService->sendRuntimeSensorEvent(event);
+ return err == OK;
+}
+
NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
JNIEnv* env, jobject listener)
: mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@
jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
}
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+ jobject callback)
+ : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+ AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+ bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+ static_cast<jint>(ns2us(samplingPeriodNs)),
+ static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
NativeSensorService* service = new NativeSensorService(env, listener);
return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@
service->unregisterProximityActiveListener();
}
-static const JNINativeMethod methods[] = {
- {
- "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
- reinterpret_cast<void*>(startSensorServiceNative)
- },
- {
- "registerProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(registerProximityActiveListenerNative)
- },
- {
- "unregisterProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
- },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+ jstring name, jstring vendor, jobject callback) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+
+static const JNINativeMethod methods[] = {
+ {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+ reinterpret_cast<void*>(startSensorServiceNative)},
+ {"registerProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+ {"unregisterProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+ {"registerRuntimeSensorNative",
+ "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+ reinterpret_cast<void*>(registerRuntimeSensorNative)},
+ {"unregisterRuntimeSensorNative", "(JI)V",
+ reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+ {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+ reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
};
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
sJvm = vm;
jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+ jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+ sMethodIdOnStateChanged =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
NELEM(methods));
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 332a75e..8854453 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -25,7 +25,6 @@
import android.credentials.ui.CreateCredentialProviderData;
import android.credentials.ui.Entry;
import android.credentials.ui.ProviderPendingIntentResponse;
-import android.os.Bundle;
import android.service.credentials.BeginCreateCredentialRequest;
import android.service.credentials.BeginCreateCredentialResponse;
import android.service.credentials.CreateCredentialRequest;
@@ -68,12 +67,11 @@
createRequestSession.mClientRequest,
createRequestSession.mClientCallingPackage);
if (providerCreateRequest != null) {
- // TODO : Replace with proper splitting of request
BeginCreateCredentialRequest providerBeginCreateRequest =
new BeginCreateCredentialRequest(
providerCreateRequest.getCallingPackage(),
providerCreateRequest.getType(),
- new Bundle());
+ createRequestSession.mClientRequest.getCandidateQueryData());
return new ProviderCreateSession(context, providerInfo, createRequestSession, userId,
remoteCredentialService, providerBeginCreateRequest, providerCreateRequest);
}
@@ -88,7 +86,7 @@
String capability = clientRequest.getType();
if (providerCapabilities.contains(capability)) {
return new CreateCredentialRequest(clientCallingPackage, capability,
- clientRequest.getData());
+ clientRequest.getCredentialData());
}
Log.i(TAG, "Unable to create provider request - capabilities do not match");
return null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 8a8485a..9cb7533 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -16,24 +16,35 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Objects;
final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
@Override
- void saveToXml(TypedXmlSerializer serializer, String attributeName, Boolean value)
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Boolean value)
throws IOException {
+ Objects.requireNonNull(value);
serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
}
+ @Nullable
@Override
- Boolean readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
- return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+ Boolean readFromXml(TypedXmlPullParser parser, String attributeName) {
+ try {
+ return parser.getAttributeBoolean(/* namespace= */ null, attributeName);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+ return null;
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 61d93c7..775e3d8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -81,6 +81,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_NOT_SUSPENDED;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_EXPLICITLY;
import static android.app.admin.DevicePolicyManager.PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT;
@@ -140,6 +141,7 @@
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -309,6 +311,7 @@
import android.provider.CalendarContract;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
@@ -712,6 +715,17 @@
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
+ private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
+ private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
+
+ /**
+ * For apps targeting U+
+ * Enable multiple admins to coexist on the same device.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ static final long ENABLE_COEXISTENCE_CHANGE = 260560985L;
+
final Context mContext;
final Injector mInjector;
final PolicyPathProvider mPathProvider;
@@ -795,6 +809,8 @@
private final DeviceManagementResourcesProvider mDeviceManagementResourcesProvider;
private final DevicePolicyManagementRoleObserver mDevicePolicyManagementRoleObserver;
+ private final DevicePolicyEngine mDevicePolicyEngine;
+
private static final boolean ENABLE_LOCK_GUARD = true;
/**
@@ -1864,6 +1880,8 @@
mUserData = new SparseArray<>();
mOwners = makeOwners(injector, pathProvider);
+ mDevicePolicyEngine = new DevicePolicyEngine(mContext);
+
if (!mHasFeature) {
// Skip the rest of the initialization
mSetupContentObserver = null;
@@ -1908,6 +1926,9 @@
mUserManagerInternal.addUserLifecycleListener(new UserLifecycleListener());
mDeviceManagementResourcesProvider.load();
+ if (isCoexistenceFlagEnabled()) {
+ mDevicePolicyEngine.load();
+ }
// The binder caches are not enabled until the first invalidation.
invalidateBinderCaches();
@@ -7951,8 +7972,17 @@
Preconditions.checkCallAuthorization(isProfileOwnerOnUser0(caller)
|| isProfileOwnerOfOrganizationOwnedDevice(caller) || isDefaultDeviceOwner(caller));
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+ if (isCoexistenceEnabled(caller)) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.AUTO_TIMEZONE,
+ // TODO(b/260573124): add correct enforcing admin when permission changes are
+ // merged.
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ enabled);
+ } else {
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
+ }
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_AUTO_TIME_ZONE)
@@ -12245,8 +12275,38 @@
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_PACKAGES);
- final int userHandle = caller.getUserId();
- setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ if (packages.length == 0) {
+ mDevicePolicyEngine.removeLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ admin,
+ caller.getUserId());
+ } else {
+ LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+ LockTaskPolicy policy;
+ if (currentPolicy == null) {
+ policy = new LockTaskPolicy(Set.of(packages));
+ } else {
+ policy = currentPolicy.clone();
+ policy.setPackages(Set.of(packages));
+ }
+
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ policy,
+ caller.getUserId());
+ }
+ } else {
+ synchronized (getLockObject()) {
+ final int userHandle = caller.getUserId();
+ setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
+ }
}
}
@@ -12267,8 +12327,21 @@
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
- final List<String> packages = getUserData(userHandle).mLockTaskPackages;
- return packages.toArray(new String[packages.size()]);
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+ if (policy == null) {
+ return new String[0];
+ } else {
+ return policy.getPackages().toArray(new String[policy.getPackages().size()]);
+ }
+ } else {
+ synchronized (getLockObject()) {
+ final List<String> packages = getUserData(userHandle).mLockTaskPackages;
+ return packages.toArray(new String[packages.size()]);
+ }
}
}
@@ -12284,8 +12357,19 @@
}
final int userId = mInjector.userHandleGetCallingUserId();
- synchronized (getLockObject()) {
- return getUserData(userId).mLockTaskPackages.contains(pkg);
+ // TODO(b/260560985): This is not the right check, as the flag could be enabled but there
+ // could be an admin that hasn't targeted U.
+ if (isCoexistenceFlagEnabled()) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userId).getCurrentResolvedPolicy();
+ if (policy == null) {
+ return false;
+ }
+ return policy.getPackages().contains(pkg);
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userId).mLockTaskPackages.contains(pkg);
+ }
}
}
@@ -12308,7 +12392,28 @@
enforceCanCallLockTaskLocked(caller);
enforceCanSetLockTaskFeaturesOnFinancedDevice(caller, flags);
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_LOCK_TASK_FEATURES);
- setLockTaskFeaturesLocked(userHandle, flags);
+ }
+ if (isCoexistenceEnabled(caller)) {
+ EnforcingAdmin admin = EnforcingAdmin.createEnterpriseEnforcingAdmin(who);
+ LockTaskPolicy currentPolicy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ caller.getUserId()).getPoliciesSetByAdmins().get(admin);
+ if (currentPolicy == null) {
+ throw new IllegalArgumentException("Can't set a lock task flags without setting "
+ + "lock task packages first.");
+ }
+ LockTaskPolicy policy = currentPolicy.clone();
+ policy.setFlags(flags);
+
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.LOCK_TASK,
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(who),
+ policy,
+ caller.getUserId());
+ } else {
+ synchronized (getLockObject()) {
+ setLockTaskFeaturesLocked(userHandle, flags);
+ }
}
}
@@ -12326,7 +12431,21 @@
final int userHandle = caller.getUserId();
synchronized (getLockObject()) {
enforceCanCallLockTaskLocked(caller);
- return getUserData(userHandle).mLockTaskFeatures;
+ }
+
+ if (isCoexistenceEnabled(caller)) {
+ LockTaskPolicy policy = mDevicePolicyEngine.getLocalPolicy(
+ PolicyDefinition.LOCK_TASK, userHandle).getCurrentResolvedPolicy();
+ if (policy == null) {
+ // We default on the power button menu, in order to be consistent with pre-P
+ // behaviour.
+ return DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ }
+ return policy.getFlags();
+ } else {
+ synchronized (getLockObject()) {
+ return getUserData(userHandle).mLockTaskFeatures;
+ }
}
}
@@ -13905,6 +14024,20 @@
if (isFinancedDeviceOwner(caller)) {
enforcePermissionGrantStateOnFinancedDevice(packageName, permission);
}
+ }
+ if (isCoexistenceEnabled(caller)) {
+ mDevicePolicyEngine.setLocalPolicy(
+ PolicyDefinition.PERMISSION_GRANT(packageName, permission),
+ // TODO(b/260573124): Add correct enforcing admin when permission changes are
+ // merged, and don't forget to handle delegates! Enterprise admins assume
+ // component name isn't null.
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(caller.getComponentName()),
+ grantState,
+ caller.getUserId());
+ // TODO: update javadoc to reflect that callback no longer return success/failure
+ callback.sendResult(Bundle.EMPTY);
+ } else {
+ synchronized (getLockObject()) {
long ident = mInjector.binderClearCallingIdentity();
try {
boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId())
@@ -13921,14 +14054,16 @@
callback.sendResult(null);
return;
}
- if (grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED
+ if (grantState == PERMISSION_GRANT_STATE_GRANTED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED
|| grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) {
AdminPermissionControlParams permissionParams =
- new AdminPermissionControlParams(packageName, permission, grantState,
+ new AdminPermissionControlParams(packageName, permission,
+ grantState,
canAdminGrantSensorsPermissionsForUser(caller.getUserId()));
mInjector.getPermissionControllerManager(caller.getUserHandle())
- .setRuntimePermissionGrantStateByDeviceAdmin(caller.getPackageName(),
+ .setRuntimePermissionGrantStateByDeviceAdmin(
+ caller.getPackageName(),
permissionParams, mContext.getMainExecutor(),
(permissionWasSet) -> {
if (isPostQAdmin && !permissionWasSet) {
@@ -13947,13 +14082,14 @@
callback.sendResult(Bundle.EMPTY);
});
- }
- } catch (SecurityException e) {
- Slogf.e(LOG_TAG, "Could not set permission grant state", e);
+ }
+ } catch (SecurityException e) {
+ Slogf.e(LOG_TAG, "Could not set permission grant state", e);
- callback.sendResult(null);
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
+ callback.sendResult(null);
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
}
}
@@ -19017,4 +19153,18 @@
return result;
});
}
+
+ // TODO(b/260560985): properly gate coexistence changes
+ private boolean isCoexistenceEnabled(CallerIdentity caller) {
+ return isCoexistenceFlagEnabled()
+ && mInjector.isChangeEnabled(
+ ENABLE_COEXISTENCE_CHANGE, caller.getPackageName(), caller.getUserId());
+ }
+
+ private boolean isCoexistenceFlagEnabled() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE_DEVICE_POLICY_MANAGER,
+ ENABLE_COEXISTENCE_FLAG,
+ DEFAULT_ENABLE_COEXISTENCE_FLAG);
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index 3152f0b..d5949dd 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -16,24 +16,35 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Objects;
final class IntegerPolicySerializer extends PolicySerializer<Integer> {
@Override
- void saveToXml(TypedXmlSerializer serializer, String attributeName, Integer value)
+ void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull Integer value)
throws IOException {
+ Objects.requireNonNull(value);
serializer.attributeInt(/* namespace= */ null, attributeName, value);
}
+ @Nullable
@Override
- Integer readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
- return parser.getAttributeInt(/* namespace= */ null, attributeName);
+ Integer readFromXml(TypedXmlPullParser parser, String attributeName) {
+ try {
+ return parser.getAttributeInt(/* namespace= */ null, attributeName);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+ return null;
+ }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
index 9360fd7..d3e8de4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicy.java
@@ -16,7 +16,10 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.DevicePolicyManager;
+import android.util.Log;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -24,19 +27,16 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
final class LockTaskPolicy {
- private Set<String> mPackages;
- private int mFlags;
+ static final int DEFAULT_LOCK_TASK_FLAG = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
+ private Set<String> mPackages = new HashSet<>();
+ private int mFlags = DEFAULT_LOCK_TASK_FLAG;
- LockTaskPolicy(@Nullable Set<String> packages, int flags) {
- mPackages = packages;
- mFlags = flags;
- }
-
- @Nullable
+ @NonNull
Set<String> getPackages() {
return mPackages;
}
@@ -45,8 +45,20 @@
return mFlags;
}
- void setPackages(Set<String> packages) {
- mPackages = packages;
+ LockTaskPolicy(Set<String> packages) {
+ Objects.requireNonNull(packages);
+ mPackages.addAll(packages);
+ }
+
+ private LockTaskPolicy(Set<String> packages, int flags) {
+ Objects.requireNonNull(packages);
+ mPackages = new HashSet<>(packages);
+ mFlags = flags;
+ }
+
+ void setPackages(@NonNull Set<String> packages) {
+ Objects.requireNonNull(packages);
+ mPackages = new HashSet<>(packages);
}
void setFlags(int flags) {
@@ -54,6 +66,13 @@
}
@Override
+ public LockTaskPolicy clone() {
+ LockTaskPolicy policy = new LockTaskPolicy(mPackages);
+ policy.setFlags(mFlags);
+ return policy;
+ }
+
+ @Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@@ -67,6 +86,11 @@
return Objects.hash(mPackages, mFlags);
}
+ @Override
+ public String toString() {
+ return "mPackages= " + String.join(", ", mPackages) + "; mFlags= " + mFlags;
+ }
+
static final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
private static final String ATTR_PACKAGES = ":packages";
@@ -74,15 +98,17 @@
private static final String ATTR_FLAGS = ":flags";
@Override
- void saveToXml(
- TypedXmlSerializer serializer, String attributeNamePrefix, LockTaskPolicy value)
- throws IOException {
- if (value.mPackages != null) {
- serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES,
- String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
+ void saveToXml(TypedXmlSerializer serializer, String attributeNamePrefix,
+ @NonNull LockTaskPolicy value) throws IOException {
+ Objects.requireNonNull(value);
+ if (value.mPackages == null || value.mPackages.isEmpty()) {
+ throw new IllegalArgumentException("Error saving LockTaskPolicy to file, lock task "
+ + "packages must be present");
}
+ serializer.attribute(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_PACKAGES,
+ String.join(ATTR_PACKAGES_SEPARATOR, value.mPackages));
serializer.attributeInt(
/* namespace= */ null,
attributeNamePrefix + ATTR_FLAGS,
@@ -90,18 +116,24 @@
}
@Override
- LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix)
- throws XmlPullParserException {
+ LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
String packagesStr = parser.getAttributeValue(
/* namespace= */ null,
attributeNamePrefix + ATTR_PACKAGES);
- Set<String> packages = packagesStr == null
- ? null
- : Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
- int flags = parser.getAttributeInt(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS);
- return new LockTaskPolicy(packages, flags);
+ if (packagesStr == null) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+ return null;
+ }
+ Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
+ try {
+ int flags = parser.getAttributeInt(
+ /* namespace= */ null,
+ attributeNamePrefix + ATTR_FLAGS);
+ return new LockTaskPolicy(packages, flags);
+ } catch (XmlPullParserException e) {
+ Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+ return null;
+ }
}
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index 3a18cb9..a787a0b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -25,8 +25,6 @@
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
@@ -225,8 +223,8 @@
mPolicySerializer.saveToXml(serializer, attributeName, value);
}
- V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException {
+ @Nullable
+ V readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
return mPolicySerializer.readFromXml(parser, attributeName);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index b645b97..74b6f9e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -29,6 +29,7 @@
import com.android.server.utils.Slogf;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
@@ -53,7 +54,7 @@
static boolean setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull String[] args) {
- Binder.withCleanCallingIdentity(() -> {
+ return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
if (args == null || args.length < 2) {
throw new IllegalArgumentException("Package name and permission name must be "
+ "provided as arguments");
@@ -84,8 +85,7 @@
// TODO: add logging
return false;
}
- });
- return true;
+ }));
}
@NonNull
@@ -106,9 +106,14 @@
static boolean setLockTask(
@Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
- DevicePolicyManagerService.updateLockTaskPackagesLocked(
- context, List.copyOf(policy.getPackages()), userId);
- DevicePolicyManagerService.updateLockTaskFeaturesLocked(policy.getFlags(), userId);
+ List<String> packages = Collections.emptyList();
+ int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
+ if (policy != null) {
+ packages = List.copyOf(policy.getPackages());
+ flags = policy.getFlags();
+ }
+ DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
+ DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
return true;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index b3259d3..528d3b0 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -16,16 +16,15 @@
package com.android.server.devicepolicy;
+import android.annotation.NonNull;
+
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
abstract class PolicySerializer<V> {
- abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ abstract void saveToXml(TypedXmlSerializer serializer, String attributeName, @NonNull V value)
throws IOException;
- abstract V readFromXml(TypedXmlPullParser parser, String attributeName)
- throws XmlPullParserException;
+ abstract V readFromXml(TypedXmlPullParser parser, String attributeName);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 5fc3cb0..d3dee98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -40,7 +40,7 @@
private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
private final PolicyDefinition<V> mPolicyDefinition;
- private final LinkedHashMap<EnforcingAdmin, V> mAdminsPolicy = new LinkedHashMap<>();
+ private final LinkedHashMap<EnforcingAdmin, V> mPoliciesSetByAdmins = new LinkedHashMap<>();
private V mCurrentResolvedPolicy;
PolicyState(@NonNull PolicyDefinition<V> policyDefinition) {
@@ -49,13 +49,13 @@
private PolicyState(
@NonNull PolicyDefinition<V> policyDefinition,
- @NonNull LinkedHashMap<EnforcingAdmin, V> adminsPolicy,
+ @NonNull LinkedHashMap<EnforcingAdmin, V> policiesSetByAdmins,
V currentEnforcedPolicy) {
Objects.requireNonNull(policyDefinition);
- Objects.requireNonNull(adminsPolicy);
+ Objects.requireNonNull(policiesSetByAdmins);
mPolicyDefinition = policyDefinition;
- mAdminsPolicy.putAll(adminsPolicy);
+ mPoliciesSetByAdmins.putAll(policiesSetByAdmins);
mCurrentResolvedPolicy = currentEnforcedPolicy;
}
@@ -63,7 +63,7 @@
* Returns {@code true} if the resolved policy has changed, {@code false} otherwise.
*/
boolean setPolicy(@NonNull EnforcingAdmin admin, @NonNull V value) {
- mAdminsPolicy.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
+ mPoliciesSetByAdmins.put(Objects.requireNonNull(admin), Objects.requireNonNull(value));
return resolvePolicy();
}
@@ -71,15 +71,19 @@
boolean removePolicy(@NonNull EnforcingAdmin admin) {
Objects.requireNonNull(admin);
- if (mAdminsPolicy.remove(admin) == null) {
+ if (mPoliciesSetByAdmins.remove(admin) == null) {
return false;
}
return resolvePolicy();
}
+ LinkedHashMap<EnforcingAdmin, V> getPoliciesSetByAdmins() {
+ return mPoliciesSetByAdmins;
+ }
+
private boolean resolvePolicy() {
- V resolvedPolicy = mPolicyDefinition.resolvePolicy(mAdminsPolicy);
+ V resolvedPolicy = mPolicyDefinition.resolvePolicy(mPoliciesSetByAdmins);
boolean policyChanged = !Objects.equals(resolvedPolicy, mCurrentResolvedPolicy);
mCurrentResolvedPolicy = resolvedPolicy;
@@ -94,14 +98,16 @@
void saveToXml(TypedXmlSerializer serializer) throws IOException {
mPolicyDefinition.saveToXml(serializer);
- mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+ if (mCurrentResolvedPolicy != null) {
+ mPolicyDefinition.savePolicyValueToXml(
+ serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy);
+ }
- for (EnforcingAdmin admin : mAdminsPolicy.keySet()) {
+ for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_POLICY_VALUE, mAdminsPolicy.get(admin));
+ serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin));
serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
admin.saveToXml(serializer);
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe505..6cdf11c 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
index 12e7cfc..212ec14 100644
--- a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -18,6 +18,11 @@
package="com.android.frameworks.inputmethodtests">
<uses-sdk android:targetSdkVersion="31" />
+ <queries>
+ <intent>
+ <action android:name="android.view.InputMethod" />
+ </intent>
+ </queries>
<!-- Permissions required for granting and logging -->
<uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
@@ -29,9 +34,23 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+ <uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
+
<application android:testOnly="true"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <service android:name="com.android.server.inputmethod.InputMethodBindingControllerTest$EmptyInputMethodService"
+ android:label="Empty IME"
+ android:permission="android.permission.BIND_INPUT_METHOD"
+ android:process=":service"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.view.InputMethod"/>
+ </intent-filter>
+ <meta-data android:name="android.view.im"
+ android:resource="@xml/method"/>
+ </service>
</application>
<instrumentation
diff --git a/services/tests/InputMethodSystemServerTests/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
new file mode 100644
index 0000000..89b06bb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/res/xml/method.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
new file mode 100644
index 0000000..42d373b
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.inputmethodservice.InputMethodService;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.view.inputmethod.InputMethodInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.inputmethod.InputBindResult;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class InputMethodBindingControllerTest extends InputMethodManagerServiceTestBase {
+
+ private static final String PACKAGE_NAME = "com.android.frameworks.inputmethodtests";
+ private static final String TEST_SERVICE_NAME =
+ "com.android.server.inputmethod.InputMethodBindingControllerTest"
+ + "$EmptyInputMethodService";
+ private static final String TEST_IME_ID = PACKAGE_NAME + "/" + TEST_SERVICE_NAME;
+ private static final long TIMEOUT_IN_SECONDS = 3;
+
+ private InputMethodBindingController mBindingController;
+ private Instrumentation mInstrumentation;
+ private final int mImeConnectionBindFlags =
+ InputMethodBindingController.IME_CONNECTION_BIND_FLAGS
+ & ~Context.BIND_SCHEDULE_LIKE_TOP_APP;
+ private CountDownLatch mCountDownLatch;
+
+ public static class EmptyInputMethodService extends InputMethodService {}
+
+ @Before
+ public void setUp() throws RemoteException {
+ super.setUp();
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mCountDownLatch = new CountDownLatch(1);
+ // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling
+ // from system.
+ mBindingController =
+ new InputMethodBindingController(
+ mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch);
+ }
+
+ @Test
+ public void testBindCurrentMethod_noIme() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(null);
+ InputBindResult result = mBindingController.bindCurrentMethod();
+ assertThat(result).isEqualTo(InputBindResult.NO_IME);
+ }
+ }
+
+ @Test
+ public void testBindCurrentMethod_unknownId() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId("unknown ime id");
+ }
+ assertThrows(IllegalArgumentException.class, () -> {
+ synchronized (ImfLock.class) {
+ mBindingController.bindCurrentMethod();
+ }
+ });
+ }
+
+ @Test
+ public void testBindCurrentMethod_notConnected() {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(TEST_IME_ID);
+ doReturn(false)
+ .when(mContext)
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ anyInt(),
+ any(UserHandle.class));
+
+ InputBindResult result = mBindingController.bindCurrentMethod();
+ assertThat(result).isEqualTo(InputBindResult.IME_NOT_CONNECTED);
+ }
+ }
+
+ @Test
+ public void testBindAndUnbindMethod() throws Exception {
+ // Bind with main connection
+ testBindCurrentMethodWithMainConnection();
+
+ // Bind with visible connection
+ testBindCurrentMethodWithVisibleConnection();
+
+ // Unbind both main and visible connections
+ testUnbindCurrentMethod();
+ }
+
+ private void testBindCurrentMethodWithMainConnection() throws Exception {
+ synchronized (ImfLock.class) {
+ mBindingController.setSelectedMethodId(TEST_IME_ID);
+ }
+ InputMethodInfo info = mInputMethodManagerService.mMethodMap.get(TEST_IME_ID);
+ assertThat(info).isNotNull();
+ assertThat(info.getId()).isEqualTo(TEST_IME_ID);
+ assertThat(info.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+
+ // Bind input method with main connection. It is called on another thread because we should
+ // wait for onServiceConnected() to finish.
+ InputBindResult result = callOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ return mBindingController.bindCurrentMethod();
+ }
+ });
+
+ verify(mContext, times(1))
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(mImeConnectionBindFlags),
+ any(UserHandle.class));
+ assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
+ assertThat(result.id).isEqualTo(info.getId());
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.hasConnection()).isTrue();
+ assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
+ assertThat(mBindingController.getCurToken()).isNotNull();
+ }
+ // Wait for onServiceConnected()
+ mCountDownLatch.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ // Verify onServiceConnected() is called and bound successfully.
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.getCurMethod()).isNotNull();
+ assertThat(mBindingController.getCurMethodUid()).isNotEqualTo(Process.INVALID_UID);
+ }
+ }
+
+ private void testBindCurrentMethodWithVisibleConnection() {
+ mInstrumentation.runOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ mBindingController.setCurrentMethodVisible();
+ }
+ });
+ // Bind input method with visible connection
+ verify(mContext, times(1))
+ .bindServiceAsUser(
+ any(Intent.class),
+ any(ServiceConnection.class),
+ eq(InputMethodBindingController.IME_VISIBLE_BIND_FLAGS),
+ any(UserHandle.class));
+ synchronized (ImfLock.class) {
+ assertThat(mBindingController.isVisibleBound()).isTrue();
+ }
+ }
+
+ private void testUnbindCurrentMethod() {
+ mInstrumentation.runOnMainSync(() -> {
+ synchronized (ImfLock.class) {
+ mBindingController.unbindCurrentMethod();
+ }
+ });
+
+ synchronized (ImfLock.class) {
+ // Unbind both main connection and visible connection
+ assertThat(mBindingController.hasConnection()).isFalse();
+ assertThat(mBindingController.isVisibleBound()).isFalse();
+ verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
+ assertThat(mBindingController.getCurToken()).isNull();
+ assertThat(mBindingController.getCurId()).isNull();
+ assertThat(mBindingController.getCurMethod()).isNull();
+ assertThat(mBindingController.getCurMethodUid()).isEqualTo(Process.INVALID_UID);
+ }
+ }
+
+ private static <V> V callOnMainSync(Callable<V> callable) {
+ AtomicReference<V> result = new AtomicReference<>();
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(
+ () -> {
+ try {
+ result.set(callable.call());
+ } catch (Exception e) {
+ throw new RuntimeException("Exception was thrown", e);
+ }
+ });
+ return result.get();
+ }
+}
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 1f66a11..7bf9a9e 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -17,7 +17,12 @@
package com.android.server.pm.test.parsing.parcelling
import android.content.Intent
-import android.content.pm.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.ConfigurationInfo
+import android.content.pm.FeatureGroupInfo
+import android.content.pm.FeatureInfo
+import android.content.pm.PackageManager
+import android.content.pm.SigningDetails
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
@@ -27,16 +32,32 @@
import com.android.internal.R
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.*
+import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.server.pm.pkg.component.ParsedComponentImpl
+import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.server.pm.pkg.component.ParsedProcessImpl
+import com.android.server.pm.pkg.component.ParsedProviderImpl
+import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import java.security.KeyPairGenerator
import java.security.PublicKey
+import java.util.UUID
import kotlin.contracts.ExperimentalContracts
@ExperimentalContracts
class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, PackageImpl::class) {
+ companion object {
+ private val TEST_UUID = UUID.fromString("57554103-df3e-4475-ae7a-8feba49353ac")
+ }
+
override val defaultImpl = PackageImpl.forTesting("com.example.test")
override val creator = PackageImpl.CREATOR
@@ -72,8 +93,6 @@
"getLongVersionCode",
// Tested through constructor
"getManifestPackageName",
- // Utility methods
- "getStorageUuid",
// Removal not tested, irrelevant for parcelling concerns
"removeUsesOptionalLibrary",
"clearAdoptPermissions",
@@ -85,6 +104,7 @@
// Tested manually
"getMimeGroups",
"getRequestedPermissions",
+ "getStorageUuid",
// Tested through asSplit
"asSplit",
"getSplits",
@@ -155,7 +175,6 @@
AndroidPackage::getResizeableActivity,
AndroidPackage::getRestrictedAccountType,
AndroidPackage::getRoundIconRes,
- PackageImpl::getSeInfo,
PackageImpl::getSecondaryCpuAbi,
AndroidPackage::getSecondaryNativeLibraryDir,
AndroidPackage::getSharedUserId,
@@ -241,7 +260,7 @@
)
override fun extraParams() = listOf(
- getter(AndroidPackage::getVolumeUuid, "57554103-df3e-4475-ae7a-8feba49353ac"),
+ getter(AndroidPackage::getVolumeUuid, TEST_UUID.toString()),
getter(AndroidPackage::isProfileable, true),
getter(PackageImpl::getVersionCode, 3),
getter(PackageImpl::getVersionCodeMajor, 9),
@@ -602,6 +621,8 @@
expect.that(after.usesStaticLibrariesCertDigests!!.size).isEqualTo(1)
expect.that(after.usesStaticLibrariesCertDigests!![0]).asList()
.containsExactly("testCertDigest2")
+
+ expect.that(after.storageUuid).isEqualTo(TEST_UUID)
}
private fun testKey() = KeyPairGenerator.getInstance("RSA")
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 51fa851..e7c384b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -252,6 +252,7 @@
noteBoot(4);
assertTrue(RescueParty.isRebootPropertySet());
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
noteBoot(5);
assertTrue(RescueParty.isFactoryResetPropertySet());
}
@@ -276,6 +277,7 @@
noteAppCrash(4, true);
assertTrue(RescueParty.isRebootPropertySet());
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
noteAppCrash(5, true);
assertTrue(RescueParty.isFactoryResetPropertySet());
}
@@ -429,6 +431,27 @@
for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
noteBoot(i + 1);
}
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ noteBoot(LEVEL_FACTORY_RESET + 1);
+ assertTrue(RescueParty.isAttemptingFactoryReset());
+ assertTrue(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
+ public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot(i + 1);
+ }
+ int mitigationCount = LEVEL_FACTORY_RESET + 1;
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ noteBoot(mitigationCount++);
+ SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
+ noteBoot(mitigationCount + 1);
assertTrue(RescueParty.isAttemptingFactoryReset());
assertTrue(RescueParty.isFactoryResetPropertySet());
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
new file mode 100644
index 0000000..ea14ffb
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -0,0 +1,282 @@
+/*
+ * 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.am;
+
+import static android.os.Process.myPid;
+import static android.os.Process.myUid;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManagerInternal;
+import android.app.IApplicationThread;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.am.ActivityManagerService.Injector;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Arrays;
+
+
+/**
+ * Tests to verify process starts are completed or timeout correctly
+ */
+@MediumTest
+@SuppressWarnings("GuardedBy")
+public class AsyncProcessStartTest {
+ private static final String TAG = "AsyncProcessStartTest";
+
+ private static final String PACKAGE = "com.foo";
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ private Context mContext;
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ @Mock
+ private DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ private PackageManagerInternal mPackageManagerInt;
+ @Mock
+ private UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ private ActivityManagerInternal mActivityManagerInt;
+ @Mock
+ private ActivityTaskManagerInternal mActivityTaskManagerInt;
+ @Mock
+ private BatteryStatsService mBatteryStatsService;
+
+ private ActivityManagerService mRealAms;
+ private ActivityManagerService mAms;
+
+ private ProcessList mRealProcessList = new ProcessList();
+ private ProcessList mProcessList;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+ LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
+
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
+
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
+ doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
+
+ mRealAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mRealAms.mAtmInternal = mActivityTaskManagerInt;
+ mRealAms.mPackageManagerInt = mPackageManagerInt;
+ mRealAms.mUsageStatsService = mUsageStatsManagerInt;
+ mRealAms.mProcessesReady = true;
+ mAms = spy(mRealAms);
+ mRealProcessList.mService = mAms;
+ mProcessList = spy(mRealProcessList);
+
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ return null;
+ }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mRealProcessList;
+ }
+
+ @Override
+ public BatteryStatsService getBatteryStatsService() {
+ return mBatteryStatsService;
+ }
+ }
+
+ private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
+ throws Exception {
+ final ApplicationInfo ai = makeApplicationInfo(packageName);
+ return makeActiveProcessRecord(ai, wedge);
+ }
+
+ private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
+ throws Exception {
+ final IApplicationThread thread = mock(IApplicationThread.class);
+ final IBinder threadBinder = new Binder();
+ doReturn(threadBinder).when(thread).asBinder();
+ doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting bindApplication() for "
+ + Arrays.toString(invocation.getArguments()));
+ if (!wedge) {
+ mRealAms.finishAttachApplication(0);
+ }
+ return null;
+ }).when(thread).bindApplication(
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ any(), anyInt(),
+ anyBoolean(), anyBoolean(),
+ anyBoolean(), anyBoolean(), any(),
+ any(), any(), any(),
+ any(), any(),
+ any(), any(),
+ any(),
+ anyLong(), anyLong());
+
+ final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
+ r.setPid(myPid());
+ r.setStartUid(myUid());
+ r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
+ r.makeActive(thread, mAms.mProcessStats);
+ doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
+ anyBoolean());
+
+ return r;
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = packageName;
+ ai.uid = myUid();
+ return ai;
+ }
+
+ /**
+ * Verify that we don't kill a normal process
+ */
+ @Test
+ public void testNormal() throws Exception {
+ ProcessRecord app = startProcessAndWait(false);
+
+ verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ /**
+ * Verify that we kill a wedged process after the process start timeout
+ */
+ @Test
+ public void testWedged() throws Exception {
+ ProcessRecord app = startProcessAndWait(true);
+
+ verify(app).killLocked(any(), anyInt(), anyBoolean());
+ }
+
+ private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
+ final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
+ final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
+
+ mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
+ /* expectedStartSeq */ 0, /* procAttached */ false);
+
+ app.getThread().bindApplication(PACKAGE, appInfo,
+ null, null,
+ null,
+ null,
+ null, null,
+ null,
+ null, 0,
+ false, false,
+ true, false,
+ null,
+ null, null,
+ null,
+ null, null, null,
+ null, null,
+ 0, 0);
+
+ // Sleep until timeout should have triggered
+ SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+
+ return app;
+ }
+}
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 66e7ec0..c87fd26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -49,6 +49,7 @@
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
+import android.appwidget.AppWidgetManager;
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
@@ -538,6 +539,59 @@
}
/**
+ * Verify that we don't let urgent broadcasts starve delivery of non-urgent
+ */
+ @Test
+ public void testUrgentStarvation() {
+ final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+ optInteractive.setInteractive(true);
+
+ mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
+ BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ // mix of broadcasts, with more than 2 fg/urgent
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+ optInteractive), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ queue.enqueueOrReplaceBroadcast(
+ makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+ optInteractive), 0);
+
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+ // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+ // and then back to prioritizing urgent ones
+ queue.makeActiveNextPending();
+ assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+ queue.getActive().intent.getAction());
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+ // verify the reset-count-then-resume worked too
+ queue.makeActiveNextPending();
+ assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+ }
+
+ /**
* Verify that sending a broadcast that removes any matching pending
* broadcasts is applied as expected.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index dc77762..9f0d759 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -17,6 +17,9 @@
package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.server.app.GameManagerService.CANCEL_GAME_LOADING_MODE;
+import static com.android.server.app.GameManagerService.LOADING_BOOST_MAX_DURATION;
+import static com.android.server.app.GameManagerService.SET_GAME_STATE;
import static com.android.server.app.GameManagerService.WRITE_SETTINGS;
import static org.junit.Assert.assertArrayEquals;
@@ -58,6 +61,7 @@
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
+import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManagerInternal;
@@ -103,7 +107,8 @@
private static final String PACKAGE_NAME_INVALID = "com.android.app";
private static final int USER_ID_1 = 1001;
private static final int USER_ID_2 = 1002;
- private static final int DEFAULT_PACKAGE_UID = 12345;
+ // to pass the valid package check in some of the server methods
+ private static final int DEFAULT_PACKAGE_UID = Binder.getCallingUid();
private MockitoSession mMockingSession;
private String mPackageName;
@@ -204,28 +209,33 @@
.startMocking();
mMockContext = new MockContext(InstrumentationRegistry.getContext());
mPackageName = mMockContext.getPackageName();
- final ApplicationInfo applicationInfo = new ApplicationInfo();
- applicationInfo.category = ApplicationInfo.CATEGORY_GAME;
- applicationInfo.packageName = mPackageName;
- final PackageInfo pi = new PackageInfo();
- pi.packageName = mPackageName;
- pi.applicationInfo = applicationInfo;
- final List<PackageInfo> packages = new ArrayList<>();
- packages.add(pi);
-
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_GAME);
final Resources resources =
InstrumentationRegistry.getInstrumentation().getContext().getResources();
when(mMockPackageManager.getResourcesForApplication(anyString()))
.thenReturn(resources);
- when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
- .thenReturn(packages);
- when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
- .thenReturn(applicationInfo);
when(mMockPackageManager.getPackageUidAsUser(mPackageName, USER_ID_1)).thenReturn(
DEFAULT_PACKAGE_UID);
LocalServices.addService(PowerManagerInternal.class, mMockPowerManager);
}
+ private void mockAppCategory(String packageName, @ApplicationInfo.Category int category)
+ throws Exception {
+ reset(mMockPackageManager);
+ final ApplicationInfo gameApplicationInfo = new ApplicationInfo();
+ gameApplicationInfo.category = category;
+ gameApplicationInfo.packageName = packageName;
+ final PackageInfo pi = new PackageInfo();
+ pi.packageName = packageName;
+ pi.applicationInfo = gameApplicationInfo;
+ final List<PackageInfo> packages = new ArrayList<>();
+ packages.add(pi);
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), anyInt()))
+ .thenReturn(packages);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(gameApplicationInfo);
+ }
+
@After
public void tearDown() throws Exception {
LocalServices.removeServiceForTest(PowerManagerInternal.class);
@@ -456,17 +466,22 @@
* By default game mode is set to STANDARD
*/
@Test
- public void testGameModeDefaultValue() {
- GameManagerService gameManagerService =
- new GameManagerService(mMockContext, mTestLooper.getLooper());
-
- startUser(gameManagerService, USER_ID_1);
+ public void testGetGameMode_defaultValue() {
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
mockModifyGameModeGranted();
-
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
}
+ @Test
+ public void testGetGameMode_nonGame() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ mockModifyGameModeGranted();
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ }
+
/**
* Test the default behaviour for a nonexistent user.
*/
@@ -613,16 +628,21 @@
int... requiredAvailableModes) {
Arrays.sort(requiredAvailableModes);
// check getAvailableGameModes
- int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName);
+ int[] reportedAvailableModes = gameManagerService.getAvailableGameModes(mPackageName,
+ USER_ID_1);
Arrays.sort(reportedAvailableModes);
assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
// check GetModeInfo.getAvailableGameModes
GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
- assertNotNull(info);
- reportedAvailableModes = info.getAvailableGameModes();
- Arrays.sort(reportedAvailableModes);
- assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+ if (requiredAvailableModes.length == 0) {
+ assertNull(info);
+ } else {
+ assertNotNull(info);
+ reportedAvailableModes = info.getAvailableGameModes();
+ Arrays.sort(reportedAvailableModes);
+ assertArrayEquals(requiredAvailableModes, reportedAvailableModes);
+ }
}
private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
@@ -725,6 +745,14 @@
GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
}
+ @Test
+ public void testDeviceConfig_nonGame() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ checkReportedAvailableGameModes(createServiceAndStartUser(USER_ID_1));
+ }
+
/**
* Override device config for performance mode exists and is valid.
*/
@@ -1442,47 +1470,98 @@
}
@Test
- public void testGameStateLoadingRequiresPerformanceMode() {
+ public void testSetGameState_loadingRequiresPerformanceMode() {
mockDeviceConfigNone();
mockModifyGameModeGranted();
- GameManagerService gameManagerService =
- new GameManagerService(mMockContext, mTestLooper.getLooper());
- startUser(gameManagerService, USER_ID_1);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
GameState gameState = new GameState(true, GameState.MODE_NONE);
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
mTestLooper.dispatchAll();
verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
}
- private void setGameState(boolean isLoading) {
+ @Test
+ public void testSetGameStateLoading_withNoDeviceConfig() {
mockDeviceConfigNone();
mockModifyGameModeGranted();
- GameManagerService gameManagerService =
- new GameManagerService(mMockContext, mTestLooper.getLooper());
- startUser(gameManagerService, USER_ID_1);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
gameManagerService.setGameMode(
mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
- int testMode = GameState.MODE_NONE;
+ assertEquals(gameManagerService.getGameMode(mPackageName, USER_ID_1),
+ GameManager.GAME_MODE_PERFORMANCE);
+ int testMode = GameState.MODE_GAMEPLAY_INTERRUPTIBLE;
int testLabel = 99;
int testQuality = 123;
- GameState gameState = new GameState(isLoading, testMode, testLabel, testQuality);
- assertEquals(isLoading, gameState.isLoading());
+ GameState gameState = new GameState(true, testMode, testLabel, testQuality);
assertEquals(testMode, gameState.getMode());
assertEquals(testLabel, gameState.getLabel());
assertEquals(testQuality, gameState.getQuality());
gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
mTestLooper.dispatchAll();
- verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, isLoading);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+ reset(mMockPowerManager);
+ assertTrue(
+ gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+ mTestLooper.moveTimeForward(GameManagerService.LOADING_BOOST_MAX_DURATION);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
}
@Test
- public void testSetGameStateLoading() {
- setGameState(true);
+ public void testSetGameStateLoading_withDeviceConfig() {
+ String configString = "mode=2,loadingBoost=2000";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.setGameMode(
+ mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ GameState gameState = new GameState(true, GameState.MODE_GAMEPLAY_INTERRUPTIBLE, 99, 123);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+ verify(mMockPowerManager, never()).setPowerMode(Mode.GAME_LOADING, false);
+ reset(mMockPowerManager);
+ assertTrue(
+ gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ mTestLooper.moveTimeForward(2000);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
}
@Test
public void testSetGameStateNotLoading() {
- setGameState(false);
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.setGameMode(
+ mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ int testMode = GameState.MODE_GAMEPLAY_UNINTERRUPTIBLE;
+ int testLabel = 99;
+ int testQuality = 123;
+ GameState gameState = new GameState(false, testMode, testLabel, testQuality);
+ assertFalse(gameState.isLoading());
+ assertEquals(testMode, gameState.getMode());
+ assertEquals(testLabel, gameState.getLabel());
+ assertEquals(testQuality, gameState.getQuality());
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertTrue(gameManagerService.mHandler.hasEqualMessages(SET_GAME_STATE, gameState));
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ assertFalse(
+ gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
+
+ @Test
+ public void testSetGameState_nonGame() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_AUDIO);
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ GameState gameState = new GameState(true, GameState.MODE_NONE);
+ gameManagerService.setGameState(mPackageName, gameState, USER_ID_1);
+ assertFalse(gameManagerService.mHandler.hasMessages(SET_GAME_STATE));
}
private List<String> readGameModeInterventionList() throws Exception {
@@ -1780,6 +1859,21 @@
}
@Test
+ public void testUpdateCustomGameModeConfiguration_nonGame() throws Exception {
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.updateCustomGameModeConfiguration(mPackageName,
+ new GameModeConfiguration.Builder().setScalingFactor(0.35f).setFpsOverride(
+ 60).build(),
+ USER_ID_1);
+ assertFalse(gameManagerService.mHandler.hasMessages(WRITE_SETTINGS));
+ GameManagerService.GamePackageConfiguration pkgConfig = gameManagerService.getConfig(
+ mPackageName, USER_ID_1);
+ assertNull(pkgConfig);
+ }
+
+ @Test
public void testWritingSettingFile_onShutdown() throws InterruptedException {
mockModifyGameModeGranted();
mockDeviceConfigAll();
@@ -1939,4 +2033,111 @@
}
folder.delete();
}
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup() {
+ String configString = "mode=2,loadingBoost=2000";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+ reset(mMockPowerManager);
+ assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ mTestLooper.moveTimeForward(2000);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ }
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup_outOfBoundBoostValue() {
+ String configString = "mode=2,loadingBoost=0:mode=3,loadingBoost=7000";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+ reset(mMockPowerManager);
+ assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ mTestLooper.moveTimeForward(100);
+ mTestLooper.dispatchAll();
+ // 0 loading boost value should still trigger max timeout
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ reset(mMockPowerManager);
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+
+ // 7000 loading boost value should exceed the max timeout of 5s and be bounded
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, true);
+ reset(mMockPowerManager);
+ assertTrue(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ mTestLooper.moveTimeForward(LOADING_BOOST_MAX_DURATION);
+ mTestLooper.dispatchAll();
+ verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME_LOADING, false);
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup_noDeviceConfig() {
+ mockDeviceConfigNone();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup_noLoadingBoostValue() {
+ mockDeviceConfigAll();
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup_nonGame() throws Exception {
+ String configString = "mode=2,loadingBoost=2000";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ mockModifyGameModeGranted();
+ mockAppCategory(mPackageName, ApplicationInfo.CATEGORY_IMAGE);
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_UNSUPPORTED,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+ gameManagerService.notifyGraphicsEnvironmentSetup(mPackageName, USER_ID_1);
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
+
+ @Test
+ public void testNotifyGraphicsEnvironmentSetup_differentApp() throws Exception {
+ String configString = "mode=2,loadingBoost=2000";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ mockModifyGameModeGranted();
+ GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
+ String someGamePkg = "some.game";
+ mockAppCategory(someGamePkg, ApplicationInfo.CATEGORY_GAME);
+ when(mMockPackageManager.getPackageUidAsUser(someGamePkg, USER_ID_1)).thenReturn(
+ DEFAULT_PACKAGE_UID + 1);
+ gameManagerService.setGameMode(someGamePkg, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(someGamePkg, USER_ID_1));
+ gameManagerService.notifyGraphicsEnvironmentSetup(someGamePkg, USER_ID_1);
+ verify(mMockPowerManager, never()).setPowerMode(anyInt(), anyBoolean());
+ assertFalse(gameManagerService.mHandler.hasMessages(CANCEL_GAME_LOADING_MODE));
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 5dc1251..be13bad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -48,7 +48,7 @@
StaticMockitoSession mSession;
@Mock
- AppOpsService.Constants mConstants;
+ AppOpsServiceImpl.Constants mConstants;
@Mock
Context mContext;
@@ -57,7 +57,7 @@
Handler mHandler;
@Mock
- AppOpsServiceInterface mLegacyAppOpsService;
+ AppOpsCheckingServiceInterface mLegacyAppOpsService;
AppOpsRestrictions mAppOpsRestrictions;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..7d4bc6f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -22,6 +22,8 @@
import static android.app.AppOpsManager.OP_READ_SMS;
import static android.app.AppOpsManager.OP_WIFI_SCAN;
import static android.app.AppOpsManager.OP_WRITE_SMS;
+import static android.app.AppOpsManager.resolvePackageName;
+import static android.os.Process.INVALID_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,6 +41,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
+import android.app.AppOpsManager;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.PackageOps;
import android.content.ContentResolver;
@@ -86,13 +89,13 @@
private File mAppOpsFile;
private Handler mHandler;
- private AppOpsService mAppOpsService;
+ private AppOpsServiceImpl mAppOpsService;
private int mMyUid;
private long mTestStartMillis;
private StaticMockitoSession mMockingSession;
private void setupAppOpsService() {
- mAppOpsService = new AppOpsService(mAppOpsFile, mHandler, spy(sContext));
+ mAppOpsService = new AppOpsServiceImpl(mAppOpsFile, mHandler, spy(sContext));
mAppOpsService.mHistoricalRegistry.systemReady(sContext.getContentResolver());
// Always approve all permission checks
@@ -161,17 +164,20 @@
@Test
public void testNoteOperationAndGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
// Note an op that's allowed.
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
// Note another op that's not allowed.
- mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
- false);
+ mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
assertContainsOp(loggedOps, OP_WRITE_SMS, -1, mTestStartMillis, MODE_ERRORED);
@@ -185,18 +191,20 @@
@Test
public void testNoteOperationAndGetOpsForPackage_controlledByDifferentOp() {
// This op controls WIFI_SCAN
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ALLOWED, null);
- assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
- null, false).getOpMode()).isEqualTo(MODE_ALLOWED);
+ assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ALLOWED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, -1,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
// Now set COARSE_LOCATION to ERRORED -> this will make WIFI_SCAN disabled as well.
- mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED);
- assertThat(mAppOpsService.noteOperation(OP_WIFI_SCAN, mMyUid, sMyPackageName, null, false,
- null, false).getOpMode()).isEqualTo(MODE_ERRORED);
+ mAppOpsService.setMode(OP_COARSE_LOCATION, mMyUid, sMyPackageName, MODE_ERRORED, null);
+ assertThat(mAppOpsService.noteOperationUnchecked(OP_WIFI_SCAN, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF)).isEqualTo(MODE_ERRORED);
assertContainsOp(getLoggedOps(), OP_WIFI_SCAN, mTestStartMillis, mTestStartMillis,
MODE_ALLOWED /* default for WIFI_SCAN; this is not changed or used in this test */);
@@ -205,11 +213,14 @@
// Tests the dumping and restoring of the in-memory state to/from XML.
@Test
public void testStatePersistence() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
- mAppOpsService.noteOperation(OP_WRITE_SMS, mMyUid, sMyPackageName, null, false, null,
- false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.setMode(OP_WRITE_SMS, mMyUid, sMyPackageName, MODE_ERRORED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
+ mAppOpsService.noteOperationUnchecked(OP_WRITE_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
mAppOpsService.writeState();
// Create a new app ops service which will initialize its state from XML.
@@ -224,8 +235,10 @@
// Tests that ops are persisted during shutdown.
@Test
public void testShutdown() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
mAppOpsService.shutdown();
// Create a new app ops service which will initialize its state from XML.
@@ -238,8 +251,10 @@
@Test
public void testGetOpsForPackage() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
// Query all ops
List<PackageOps> loggedOps = mAppOpsService.getOpsForPackage(
@@ -267,8 +282,10 @@
@Test
public void testPackageRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
@@ -322,8 +339,10 @@
@Test
public void testUidRemoved() {
- mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED);
- mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null, false);
+ mAppOpsService.setMode(OP_READ_SMS, mMyUid, sMyPackageName, MODE_ALLOWED, null);
+ mAppOpsService.noteOperationUnchecked(OP_READ_SMS, mMyUid,
+ resolvePackageName(mMyUid, sMyPackageName), null,
+ INVALID_UID, null, null, AppOpsManager.OP_FLAG_SELF);
List<PackageOps> loggedOps = getLoggedOps();
assertContainsOp(loggedOps, OP_READ_SMS, mTestStartMillis, -1, MODE_ALLOWED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 98e895a..3efd5e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -76,7 +76,7 @@
ActivityManagerInternal mAmi;
@Mock
- AppOpsService.Constants mConstants;
+ AppOpsServiceImpl.Constants mConstants;
AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
index e08a715..298dbf4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -93,12 +93,13 @@
}
}
- private void assertSameModes(SparseArray<AppOpsService.UidState> uidStates, int op1, int op2) {
+ private void assertSameModes(SparseArray<AppOpsServiceImpl.UidState> uidStates,
+ int op1, int op2) {
int numberOfNonDefaultOps = 0;
final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
for(int i = 0; i < uidStates.size(); i++) {
- final AppOpsService.UidState uidState = uidStates.valueAt(i);
+ final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
SparseIntArray opModes = uidState.getNonDefaultUidModes();
if (opModes != null) {
final int uidMode1 = opModes.get(op1, defaultModeOp1);
@@ -112,12 +113,12 @@
continue;
}
for (int j = 0; j < uidState.pkgOps.size(); j++) {
- final AppOpsService.Ops ops = uidState.pkgOps.valueAt(j);
+ final AppOpsServiceImpl.Ops ops = uidState.pkgOps.valueAt(j);
if (ops == null) {
continue;
}
- final AppOpsService.Op _op1 = ops.get(op1);
- final AppOpsService.Op _op2 = ops.get(op2);
+ final AppOpsServiceImpl.Op _op1 = ops.get(op1);
+ final AppOpsServiceImpl.Op _op2 = ops.get(op2);
final int mode1 = (_op1 == null) ? defaultModeOp1 : _op1.getMode();
final int mode2 = (_op2 == null) ? defaultModeOp2 : _op2.getMode();
assertEquals(mode1, mode2);
@@ -158,8 +159,8 @@
// Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
- AppOpsService testService = spy(
- new AppOpsService(mAppOpsFile, mHandler, testContext)); // trigger upgrade
+ AppOpsServiceImpl testService = spy(
+ new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
mHandler.removeCallbacks(testService.mWriteRunner);
diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
new file mode 100644
index 0000000..7ab1363
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.cpu;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ServiceManager;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public final class CpuMonitorServiceTest extends ExtendedMockitoTestCase {
+ private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG =
+ new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+ .addThreshold(30).addThreshold(70).build();
+
+ private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 =
+ new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL)
+ .addThreshold(10).addThreshold(90).build();
+
+ @Mock
+ private Context mContext;
+ private CpuMonitorService mService;
+ private HandlerExecutor mHandlerExecutor;
+ private CpuMonitorInternal mLocalService;
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.mockStatic(ServiceManager.class);
+ }
+
+ @Before
+ public void setUp() {
+ mService = new CpuMonitorService(mContext);
+ mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper()));
+ doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class),
+ anyBoolean(), anyInt()));
+ mService.onStart();
+ mLocalService = LocalServices.getService(CpuMonitorInternal.class);
+ }
+
+ @After
+ public void tearDown() {
+ // The CpuMonitorInternal.class service is added by the mService.onStart call.
+ // Remove the service to ensure the setUp procedure can add this service again.
+ LocalServices.removeServiceForTest(CpuMonitorInternal.class);
+ }
+
+ @Test
+ public void testAddRemoveCpuAvailabilityCallback() {
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+ CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+ mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+ TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+ // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and
+ // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added.
+
+ mLocalService.removeCpuAvailabilityCallback(mockCallback);
+ }
+
+
+ @Test
+ public void testDuplicateAddCpuAvailabilityCallback() {
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+ CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+ mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+ TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback);
+
+ mLocalService.addCpuAvailabilityCallback(mHandlerExecutor,
+ TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback);
+
+ // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability
+ // thresholds cross the bounds specified in the
+ // {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config.
+
+ mLocalService.removeCpuAvailabilityCallback(mockCallback);
+ }
+
+ @Test
+ public void testRemoveInvalidCpuAvailabilityCallback() {
+ CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock(
+ CpuMonitorInternal.CpuAvailabilityCallback.class);
+
+ mLocalService.removeCpuAvailabilityCallback(mockCallback);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index d41ac70..395e6ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -161,6 +161,10 @@
when(mMockedResources.getIntArray(
com.android.internal.R.array.config_autoBrightnessLevels))
.thenReturn(new int[]{});
+ when(mMockedResources.obtainTypedArray(R.array.config_displayShapeArray))
+ .thenReturn(mockArray);
+ when(mMockedResources.obtainTypedArray(R.array.config_builtInDisplayIsRoundArray))
+ .thenReturn(mockArray);
}
@After
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
index a97491d..ddfbf16 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/AsyncUserVisibilityListener.java
@@ -30,7 +30,6 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-
/**
* {@link UserVisibilityListener} implementation that expects callback events to be asynchronously
* received.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 5c3d695..01674bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
@@ -107,12 +108,16 @@
@Mock
private BackgroundDexOptJobService mJobServiceForIdle;
- private final JobParameters mJobParametersForPostBoot = new JobParameters(null,
- BackgroundDexOptService.JOB_POST_BOOT_UPDATE, null, null, null,
- 0, false, false, null, null, null);
- private final JobParameters mJobParametersForIdle = new JobParameters(null,
- BackgroundDexOptService.JOB_IDLE_OPTIMIZE, null, null, null,
- 0, false, false, null, null, null);
+ private final JobParameters mJobParametersForPostBoot =
+ createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE);
+ private final JobParameters mJobParametersForIdle =
+ createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE);
+
+ private static JobParameters createJobParameters(int jobId) {
+ JobParameters params = mock(JobParameters.class);
+ when(params.getJobId()).thenReturn(jobId);
+ return params;
+ }
private BackgroundDexOptService mService;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index dd6c733..27d0662 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -139,7 +139,7 @@
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties::class.java)
.mockStatic(SystemConfig::class.java)
- .mockStatic(SELinuxMMAC::class.java)
+ .mockStatic(SELinuxMMAC::class.java, Mockito.CALLS_REAL_METHODS)
.mockStatic(FallbackCategoryProvider::class.java)
.mockStatic(PackageManagerServiceUtils::class.java)
.mockStatic(Environment::class.java)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index c203831..4487d13 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -329,6 +329,15 @@
listener.verify();
}
+ @Test
+ public final void testOnSystemUserVisibilityChanged() throws Exception {
+ AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_SYSTEM));
+
+ mMediator.onSystemUserVisibilityChanged(/* visible= */ true);
+
+ listener.verify();
+ }
+
/**
* Starts a user in foreground on the default display, asserting it was properly started.
*
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
index ae25c1b..cb59d37 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/SlogfTest.java
@@ -44,7 +44,7 @@
private MockitoSession mSession;
- private final Exception mException = new Exception("D'OH!");
+ private final Throwable mThrowable = new Throwable("D'OH!");
@Before
public void setup() {
@@ -78,10 +78,10 @@
}
@Test
- public void testV_msgAndException() {
- Slogf.v(TAG, "msg", mException);
+ public void testV_msgAndThrowable() {
+ Slogf.v(TAG, "msg", mThrowable);
- verify(()-> Slog.v(TAG, "msg", mException));
+ verify(()-> Slog.v(TAG, "msg", mThrowable));
}
@Test
@@ -103,12 +103,12 @@
}
@Test
- public void testV_msgFormattedWithException_enabled() {
+ public void testV_msgFormattedWithThrowable_enabled() {
enableLogging(Log.VERBOSE);
- Slogf.v(TAG, mException, "msg in a %s", "bottle");
+ Slogf.v(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.v(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.v(TAG, "msg in a bottle", mThrowable));
}
@Test
@@ -128,10 +128,10 @@
}
@Test
- public void testD_msgAndException() {
- Slogf.d(TAG, "msg", mException);
+ public void testD_msgAndThrowable() {
+ Slogf.d(TAG, "msg", mThrowable);
- verify(()-> Slog.d(TAG, "msg", mException));
+ verify(()-> Slog.d(TAG, "msg", mThrowable));
}
@Test
@@ -153,19 +153,19 @@
}
@Test
- public void testD_msgFormattedWithException_enabled() {
+ public void testD_msgFormattedWithThrowable_enabled() {
enableLogging(Log.DEBUG);
- Slogf.d(TAG, mException, "msg in a %s", "bottle");
+ Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.d(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.d(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testD_msgFormattedWithException_disabled() {
disableLogging(Log.DEBUG);
- Slogf.d(TAG, mException, "msg in a %s", "bottle");
+ Slogf.d(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.d(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -178,10 +178,10 @@
}
@Test
- public void testI_msgAndException() {
- Slogf.i(TAG, "msg", mException);
+ public void testI_msgAndThrowable() {
+ Slogf.i(TAG, "msg", mThrowable);
- verify(()-> Slog.i(TAG, "msg", mException));
+ verify(()-> Slog.i(TAG, "msg", mThrowable));
}
@Test
@@ -203,19 +203,19 @@
}
@Test
- public void testI_msgFormattedWithException_enabled() {
+ public void testI_msgFormattedWithThrowable_enabled() {
enableLogging(Log.INFO);
- Slogf.i(TAG, mException, "msg in a %s", "bottle");
+ Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.i(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.i(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testI_msgFormattedWithException_disabled() {
disableLogging(Log.INFO);
- Slogf.i(TAG, mException, "msg in a %s", "bottle");
+ Slogf.i(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.i(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -228,17 +228,17 @@
}
@Test
- public void testW_msgAndException() {
- Slogf.w(TAG, "msg", mException);
+ public void testW_msgAndThrowable() {
+ Slogf.w(TAG, "msg", mThrowable);
- verify(()-> Slog.w(TAG, "msg", mException));
+ verify(()-> Slog.w(TAG, "msg", mThrowable));
}
@Test
- public void testW_exception() {
- Slogf.w(TAG, mException);
+ public void testW_Throwable() {
+ Slogf.w(TAG, mThrowable);
- verify(()-> Slog.w(TAG, mException));
+ verify(()-> Slog.w(TAG, mThrowable));
}
@Test
@@ -260,19 +260,19 @@
}
@Test
- public void testW_msgFormattedWithException_enabled() {
+ public void testW_msgFormattedWithThrowable_enabled() {
enableLogging(Log.WARN);
- Slogf.w(TAG, mException, "msg in a %s", "bottle");
+ Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.w(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.w(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testW_msgFormattedWithException_disabled() {
disableLogging(Log.WARN);
- Slogf.w(TAG, mException, "msg in a %s", "bottle");
+ Slogf.w(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.w(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -285,10 +285,10 @@
}
@Test
- public void testE_msgAndException() {
- Slogf.e(TAG, "msg", mException);
+ public void testE_msgAndThrowable() {
+ Slogf.e(TAG, "msg", mThrowable);
- verify(()-> Slog.e(TAG, "msg", mException));
+ verify(()-> Slog.e(TAG, "msg", mThrowable));
}
@Test
@@ -310,19 +310,19 @@
}
@Test
- public void testE_msgFormattedWithException_enabled() {
+ public void testE_msgFormattedWithThrowable_enabled() {
enableLogging(Log.ERROR);
- Slogf.e(TAG, mException, "msg in a %s", "bottle");
+ Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.e(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.e(TAG, "msg in a bottle", mThrowable));
}
@Test
public void testE_msgFormattedWithException_disabled() {
disableLogging(Log.ERROR);
- Slogf.e(TAG, mException, "msg in a %s", "bottle");
+ Slogf.e(TAG, mThrowable, "msg in a %s", "bottle");
verify(()-> Slog.e(eq(TAG), any(String.class), any(Throwable.class)), never());
}
@@ -335,17 +335,17 @@
}
@Test
- public void testWtf_msgAndException() {
- Slogf.wtf(TAG, "msg", mException);
+ public void testWtf_msgAndThrowable() {
+ Slogf.wtf(TAG, "msg", mThrowable);
- verify(()-> Slog.wtf(TAG, "msg", mException));
+ verify(()-> Slog.wtf(TAG, "msg", mThrowable));
}
@Test
- public void testWtf_exception() {
- Slogf.wtf(TAG, mException);
+ public void testWtf_Throwable() {
+ Slogf.wtf(TAG, mThrowable);
- verify(()-> Slog.wtf(TAG, mException));
+ verify(()-> Slog.wtf(TAG, mThrowable));
}
@Test
@@ -377,10 +377,10 @@
}
@Test
- public void testWtf_msgFormattedWithException() {
- Slogf.wtf(TAG, mException, "msg in a %s", "bottle");
+ public void testWtf_msgFormattedWithThrowable() {
+ Slogf.wtf(TAG, mThrowable, "msg in a %s", "bottle");
- verify(()-> Slog.wtf(TAG, "msg in a bottle", mException));
+ verify(()-> Slog.wtf(TAG, "msg in a bottle", mThrowable));
}
private void enableLogging(@Log.Level int level) {
diff --git a/services/tests/servicestests/src/com/android/server/DockObserverTest.java b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
index c325778..ee09074 100644
--- a/services/tests/servicestests/src/com/android/server/DockObserverTest.java
+++ b/services/tests/servicestests/src/com/android/server/DockObserverTest.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.os.Looper;
+import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -74,6 +75,11 @@
.isEqualTo(Intent.EXTRA_DOCK_STATE_UNDOCKED);
}
+ void setDeviceProvisioned(boolean provisioned) {
+ Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED,
+ provisioned ? 1 : 0);
+ }
+
@Before
public void setUp() {
if (Looper.myLooper() == null) {
@@ -131,4 +137,25 @@
assertDockEventIntentWithExtraThenUndock(observer, "DOCK=1\nKEY5=5",
Intent.EXTRA_DOCK_STATE_HE_DESK);
}
+
+ @Test
+ public void testDockIntentBroadcast_deviceNotProvisioned()
+ throws ExecutionException, InterruptedException {
+ DockObserver observer = new DockObserver(mInterceptingContext);
+ // Set the device as not provisioned.
+ setDeviceProvisioned(false);
+ observer.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+
+ BroadcastInterceptingContext.FutureIntent futureIntent =
+ updateExtconDockState(observer, "DOCK=1");
+ TestableLooper.get(this).processAllMessages();
+ // Verify no broadcast was sent as device was not provisioned.
+ futureIntent.assertNotReceived();
+
+ // Ensure we send the broadcast when the device is provisioned.
+ setDeviceProvisioned(true);
+ TestableLooper.get(this).processAllMessages();
+ assertThat(futureIntent.get().getIntExtra(Intent.EXTRA_DOCK_STATE, -1))
+ .isEqualTo(Intent.EXTRA_DOCK_STATE_DESK);
+ }
}
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 a49214f..e8b8253 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,9 +38,6 @@
import static com.android.server.am.UserController.USER_CURRENT_MSG;
import static com.android.server.am.UserController.USER_START_MSG;
import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
-import static com.android.server.am.UserController.USER_VISIBILITY_CHANGED_MSG;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
@@ -102,7 +99,6 @@
import com.android.server.SystemService;
import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerInternal;
-import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -162,18 +158,12 @@
REPORT_USER_SWITCH_MSG,
USER_SWITCH_TIMEOUT_MSG,
USER_START_MSG,
- USER_VISIBILITY_CHANGED_MSG,
USER_CURRENT_MSG);
- private static final Set<Integer> START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
+ private static final Set<Integer> START_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
USER_START_MSG,
REPORT_LOCKED_BOOT_COMPLETE_MSG);
- private static final Set<Integer> START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES = newHashSet(
- USER_START_MSG,
- USER_VISIBILITY_CHANGED_MSG,
- REPORT_LOCKED_BOOT_COMPLETE_MSG);
-
@Before
public void setUp() throws Exception {
runWithDexmakerShareClassLoader(() -> {
@@ -225,14 +215,12 @@
@Test
public void testStartUser_background() {
- mockAssignUserToMainDisplay(TEST_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions(/*visible= */ false);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID, Display.DEFAULT_DISPLAY);
}
@@ -267,7 +255,7 @@
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
verify(mInjector, never()).clearAllLockedTasks(anyString());
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
}
@Test
@@ -293,8 +281,6 @@
@Test
public void testStartPreCreatedUser_background() throws Exception {
- mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
// Make sure no intents have been fired for pre-created users.
assertTrue(mInjector.mSentIntents.isEmpty());
@@ -322,10 +308,8 @@
assertEquals("Unexpected message sent", expectedMessageCodes, actualCodes);
}
- private void startBackgroundUserAssertions(boolean visible) {
- startUserAssertions(START_BACKGROUND_USER_ACTIONS,
- visible ? START_VISIBLE_BACKGROUND_USER_MESSAGE_CODES
- : START_INVISIBLE_BACKGROUND_USER_MESSAGE_CODES);
+ private void startBackgroundUserAssertions() {
+ startUserAssertions(START_BACKGROUND_USER_ACTIONS, START_BACKGROUND_USER_MESSAGE_CODES);
}
private void startForegroundUserAssertions() {
@@ -433,7 +417,7 @@
verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
@Test
@@ -454,7 +438,7 @@
verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
continueUserSwitchAssertions(TEST_USER_ID, false);
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
@Test
@@ -561,7 +545,7 @@
assertFalse(mUserController.canStartMoreUsers());
assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
mUserController.getRunningUsersLU());
- verifySystemUserVisibilityChangedNotified(/* visible= */ false);
+ verifySystemUserVisibilityChangesNeverNotified();
}
/**
@@ -709,24 +693,19 @@
@Test
public void testStartProfile() throws Exception {
- mockAssignUserToMainDisplay(TEST_PRE_CREATED_USER_ID, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@Test
public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception {
- mockAssignUserToMainDisplay(TEST_USER_ID1, /* foreground= */ false,
- USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
mockIsUsersOnSecondaryDisplaysEnabled(true);
setUpAndStartProfileInBackground(TEST_USER_ID1);
- startBackgroundUserAssertions(/*visible= */ true);
+ startBackgroundUserAssertions();
verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY);
}
@@ -983,13 +962,6 @@
when(mInjector.isUsersOnSecondaryDisplaysEnabled()).thenReturn(value);
}
- private void mockAssignUserToMainDisplay(@UserIdInt int userId, boolean foreground,
- @UserAssignmentResult int result) {
- when(mInjector.mUserManagerInternalMock.assignUserToDisplayOnStart(eq(userId),
- /* profileGroupId= */ anyInt(), eq(foreground), eq(Display.DEFAULT_DISPLAY)))
- .thenReturn(result);
- }
-
private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
anyBoolean(), eq(displayId));
@@ -1008,8 +980,8 @@
verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplayOnStop(userId);
}
- private void verifySystemUserVisibilityChangedNotified(boolean visible) {
- verify(mInjector).onUserVisibilityChanged(UserHandle.USER_SYSTEM, visible);
+ private void verifySystemUserVisibilityChangesNeverNotified() {
+ verify(mInjector, never()).onSystemUserVisibilityChanged(anyBoolean());
}
// Should be public to allow mocking
@@ -1154,8 +1126,8 @@
}
@Override
- void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
- Log.i(TAG, "onUserVisibilityChanged(" + userId + ", " + visible + ")");
+ void onSystemUserVisibilityChanged(boolean visible) {
+ Log.i(TAG, "onSystemUserVisibilityChanged(" + visible + ")");
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 0000000..ef8a49f
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+ private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+ private static final int SENSOR_HANDLE = 7;
+
+ @Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ private SensorController mSensorController;
+ private VirtualSensorEvent mSensorEvent;
+ private VirtualSensorConfig mVirtualSensorConfig;
+ private IBinder mSensorToken;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+ mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+ mVirtualSensorConfig =
+ new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+ .build();
+ mSensorToken = new Binder("sensorToken");
+ }
+
+ @Test
+ public void createSensor_invalidHandle_throwsException() {
+ doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+
+ Throwable thrown = assertThrows(
+ RuntimeException.class,
+ () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+ assertThat(thrown.getCause().getMessage())
+ .contains("Received an invalid virtual sensor handle");
+ }
+
+ @Test
+ public void createSensor_success() {
+ doCreateSensorSuccessfully();
+
+ assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+ }
+
+ @Test
+ public void sendSensorEvent_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.sendSensorEvent(
+ new Binder("invalidSensorToken"), mSensorEvent));
+ }
+
+ @Test
+ public void sendSensorEvent_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+ verify(mSensorManagerInternalMock).sendSensorEvent(
+ SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+ mSensorEvent.getValues());
+ }
+
+ @Test
+ public void unregisterSensor_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+ }
+
+ @Test
+ public void unregisterSensor_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.unregisterSensor(mSensorToken);
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ }
+
+ private void doCreateSensorSuccessfully() {
+ doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+ mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0bd6f2c..3e8a070 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -58,6 +59,7 @@
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
+import android.hardware.Sensor;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.VirtualKeyEvent;
@@ -88,6 +90,7 @@
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
import org.junit.Before;
import org.junit.Test;
@@ -126,16 +129,19 @@
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
private static final String PHYS = "phys";
- private static final int DEVICE_ID = 42;
+ private static final int DEVICE_ID = 53;
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
+ private static final int SENSOR_HANDLE = 64;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+ private static final int VIRTUAL_DEVICE_ID = 42;
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
+ private SensorController mSensorController;
private AssociationInfo mAssociationInfo;
private VirtualDeviceManagerService mVdms;
private VirtualDeviceManagerInternal mLocalService;
@@ -150,6 +156,8 @@
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ @Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -228,6 +236,9 @@
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = UNIQUE_ID;
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -252,6 +263,7 @@
mInputController = new InputController(new Object(), mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -264,9 +276,9 @@
.setBlockedActivities(getBlockedActivities())
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
}
@@ -305,12 +317,12 @@
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
.setBlockedActivities(getBlockedActivities())
- .addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
assertThat(
@@ -576,6 +588,18 @@
}
@Test
+ public void createVirtualSensor_noPermission_failsSecurityException() {
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualSensor(
+ BINDER,
+ new VirtualSensorConfig.Builder(
+ Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+ }
+
+ @Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -679,6 +703,17 @@
}
@Test
+ public void close_cleanSensorController() {
+ mSensorController.addSensorForTesting(
+ BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+ mDeviceImpl.close();
+
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ }
+
+ @Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 036b6df..aefe4b6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -16,9 +16,14 @@
package com.android.server.companion.virtual;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
+import static android.hardware.Sensor.TYPE_ACCELEROMETER;
+
import static com.google.common.truth.Truth.assertThat;
import android.companion.virtual.VirtualDeviceParams;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.os.Parcel;
import android.os.UserHandle;
@@ -27,18 +32,25 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.List;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class VirtualDeviceParamsTest {
+ private static final String SENSOR_NAME = "VirtualSensorName";
+ private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+
@Test
public void parcelable_shouldRecreateSuccessfully() {
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
- .addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
- VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
+ .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
+ .addVirtualSensorConfig(
+ new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
+ .setVendor(SENSOR_VENDOR)
+ .build())
.build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
@@ -49,7 +61,14 @@
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
- assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
- .isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
+ assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
+
+ List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
+ assertThat(sensorConfigs).hasSize(1);
+ VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
+ assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
+ assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
+ assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
+ assertThat(sensorConfig.getStateChangeCallback()).isNull();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 062bde8..ce35626 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -171,22 +171,6 @@
private final DisplayManagerService.Injector mBasicInjector = new BasicInjector();
- private final DisplayManagerService.Injector mAllowNonNativeRefreshRateOverrideInjector =
- new BasicInjector() {
- @Override
- boolean getAllowNonNativeRefreshRateOverride() {
- return true;
- }
- };
-
- private final DisplayManagerService.Injector mDenyNonNativeRefreshRateOverrideInjector =
- new BasicInjector() {
- @Override
- boolean getAllowNonNativeRefreshRateOverride() {
- return false;
- }
- };
-
@Mock InputManagerInternal mMockInputManagerInternal;
@Mock VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal;
@Mock IVirtualDisplayCallback.Stub mMockAppToken;
@@ -408,6 +392,75 @@
assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
}
+ @Test
+ public void testCreateVirtualDisplayOwnFocus() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+
+ // This is effectively the DisplayManager service published to ServiceManager.
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ String uniqueId = "uniqueId --- Own Focus Test";
+ int width = 600;
+ int height = 800;
+ int dpi = 320;
+ int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+ | DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+
+ when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn(
+ PackageManager.PERMISSION_GRANTED);
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi);
+ builder.setFlags(flags);
+ builder.setUniqueId(uniqueId);
+ int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+ /* projection= */ null, PACKAGE_NAME);
+
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+ // flush the handler
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+ assertNotNull(ddi);
+ assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0);
+ }
+
+ @Test
+ public void testCreateVirtualDisplayOwnFocus_nonTrustedDisplay() {
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ registerDefaultDisplays(displayManager);
+
+ // This is effectively the DisplayManager service published to ServiceManager.
+ DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+ String uniqueId = "uniqueId --- Own Focus Test -- nonTrustedDisplay";
+ int width = 600;
+ int height = 800;
+ int dpi = 320;
+ int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
+
+ when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+ final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+ VIRTUAL_DISPLAY_NAME, width, height, dpi);
+ builder.setFlags(flags);
+ builder.setUniqueId(uniqueId);
+ int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+ /* projection= */ null, PACKAGE_NAME);
+
+ displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+ // flush the handler
+ displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+ DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+ assertNotNull(ddi);
+ assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0);
+ }
+
/**
* Tests that the virtual display is created along-side the default display.
*/
@@ -1044,13 +1097,32 @@
}
/**
- * Tests that the frame rate override is updated accordingly to the
- * allowNonNativeRefreshRateOverride policy.
+ * Tests that the frame rate override is returning the correct value from
+ * DisplayInfo#getRefreshRate
*/
@Test
public void testDisplayInfoNonNativeFrameRateOverride() throws Exception {
- testDisplayInfoNonNativeFrameRateOverride(mDenyNonNativeRefreshRateOverrideInjector);
- testDisplayInfoNonNativeFrameRateOverride(mAllowNonNativeRefreshRateOverrideInjector);
+ DisplayManagerService displayManager =
+ new DisplayManagerService(mContext, mBasicInjector);
+ DisplayManagerService.BinderService displayManagerBinderService =
+ displayManager.new BinderService();
+ registerDefaultDisplays(displayManager);
+ displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+ FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+ new float[]{60f});
+ int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+ displayDevice);
+ DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+ updateFrameRateOverride(displayManager, displayDevice,
+ new DisplayEventReceiver.FrameRateOverride[]{
+ new DisplayEventReceiver.FrameRateOverride(
+ Process.myUid(), 20f)
+ });
+ displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+ assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
}
/**
@@ -1078,10 +1150,7 @@
@Test
@DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
public void testDisplayInfoNonNativeFrameRateOverrideModeCompat() throws Exception {
- testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ false);
- testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ false);
+ testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ false);
}
/**
@@ -1090,10 +1159,7 @@
@Test
@EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
public void testDisplayInfoNonNativeFrameRateOverrideMode() throws Exception {
- testDisplayInfoNonNativeFrameRateOverrideMode(mDenyNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ true);
- testDisplayInfoNonNativeFrameRateOverrideMode(mAllowNonNativeRefreshRateOverrideInjector,
- /*compatChangeEnabled*/ true);
+ testDisplayInfoNonNativeFrameRateOverrideMode(/*compatChangeEnabled*/ true);
}
/**
@@ -1316,10 +1382,9 @@
assertEquals(expectedMode, displayInfo.getMode());
}
- private void testDisplayInfoNonNativeFrameRateOverrideMode(
- DisplayManagerService.Injector injector, boolean compatChangeEnabled) {
+ private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
DisplayManagerService displayManager =
- new DisplayManagerService(mContext, injector);
+ new DisplayManagerService(mContext, mBasicInjector);
DisplayManagerService.BinderService displayManagerBinderService =
displayManager.new BinderService();
registerDefaultDisplays(displayManager);
@@ -1341,40 +1406,12 @@
Display.Mode expectedMode;
if (compatChangeEnabled) {
expectedMode = new Display.Mode(1, 100, 200, 60f);
- } else if (injector.getAllowNonNativeRefreshRateOverride()) {
- expectedMode = new Display.Mode(255, 100, 200, 20f);
} else {
- expectedMode = new Display.Mode(1, 100, 200, 60f);
+ expectedMode = new Display.Mode(255, 100, 200, 20f);
}
assertEquals(expectedMode, displayInfo.getMode());
}
- private void testDisplayInfoNonNativeFrameRateOverride(
- DisplayManagerService.Injector injector) {
- DisplayManagerService displayManager =
- new DisplayManagerService(mContext, injector);
- DisplayManagerService.BinderService displayManagerBinderService =
- displayManager.new BinderService();
- registerDefaultDisplays(displayManager);
- displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
-
- FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
- new float[]{60f});
- int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
- displayDevice);
- DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
- assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
-
- updateFrameRateOverride(displayManager, displayDevice,
- new DisplayEventReceiver.FrameRateOverride[]{
- new DisplayEventReceiver.FrameRateOverride(
- Process.myUid(), 20f)
- });
- displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
- float expectedRefreshRate = injector.getAllowNonNativeRefreshRateOverride() ? 20f : 60f;
- assertEquals(expectedRefreshRate, displayInfo.getRefreshRate(), 0.01f);
- }
-
private int getDisplayIdForDisplayDevice(
DisplayManagerService displayManager,
DisplayManagerService.BinderService displayManagerBinderService,
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index c81db92..6258d6d 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -2014,6 +2014,50 @@
}
@Test
+ public void testMultiDisplay_defaultDozing_addNewDisplayDefaultGoesBackToDoze() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplay = Display.DEFAULT_DISPLAY + 1;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplay)).thenReturn(info);
+
+ doAnswer(inv -> {
+ when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
+ return null;
+ }).when(mDreamManagerInternalMock).startDream(anyBoolean(), anyString());
+
+ createService();
+ startSystem();
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+
+ forceDozing();
+ advanceTime(500);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock).startDream(eq(true), anyString());
+
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+ advanceTime(500);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId)).isEqualTo(
+ WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
+ WAKEFULNESS_DOZING);
+ verify(mDreamManagerInternalMock, times(2)).startDream(eq(true), anyString());
+ }
+
+ @Test
public void testLastSleepTime_notUpdatedWhenDreaming() {
createService();
startSystem();
diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
index aafc16d..febbffe 100644
--- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
@@ -71,9 +71,10 @@
}
@Test
- public void testThatPrintWriterProducesEmptyListFromEmptyLog() {
+ public void testThatPrintWriterProducesOnlyTitleFromEmptyLog() {
mEventLogger.dump(mTestPrintWriter);
- assertThat(mTestStringWriter.toString()).isEmpty();
+ assertThat(mTestStringWriter.toString())
+ .isEqualTo(mEventLogger.getDumpTitle() + "\n");
}
}
@@ -87,27 +88,27 @@
// insertion order, max size is 3
new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 },
// expected events
- new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_1 }
+ new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2 }
},
{
// insertion order, max size is 3
new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 },
// expected events
- new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_1 }
+ new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_3, TEST_EVENT_2 }
},
{
// insertion order, max size is 3
new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
TEST_EVENT_4 },
// expected events
- new EventLogger.Event[] { TEST_EVENT_4, TEST_EVENT_3, TEST_EVENT_2 }
+ new EventLogger.Event[] { TEST_EVENT_2, TEST_EVENT_3, TEST_EVENT_4 }
},
{
// insertion order, max size is 3
new EventLogger.Event[] { TEST_EVENT_1, TEST_EVENT_2, TEST_EVENT_3,
TEST_EVENT_4, TEST_EVENT_5 },
// expected events
- new EventLogger.Event[] { TEST_EVENT_5, TEST_EVENT_4, TEST_EVENT_3 }
+ new EventLogger.Event[] { TEST_EVENT_3, TEST_EVENT_4, TEST_EVENT_5 }
}
});
}
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 af10b9d..d758e71 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -52,9 +52,9 @@
@RunWith(AndroidTestingRunner.class)
public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
private NotificationHistoryJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
index 3a6c0eb..a83eb00 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java
@@ -44,9 +44,9 @@
@RunWith(AndroidTestingRunner.class)
public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase {
private ReviewNotificationPermissionsJobService mJobService;
- private JobParameters mJobParams = new JobParameters(null,
- ReviewNotificationPermissionsJobService.JOB_ID, null, null, null,
- 0, false, false, null, null, null);
+
+ @Mock
+ private JobParameters mJobParams;
@Captor
ArgumentCaptor<JobInfo> mJobInfoCaptor;
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 1ab7d7e..a410eed 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1206,6 +1206,25 @@
}
}
+ @Test
+ public void testFinishActivityIfPossible_sendResultImmediatelyIfResumed() {
+ final Task task = new TaskBuilder(mSupervisor).build();
+ final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task);
+ final TaskFragment taskFragment2 = createTaskFragmentWithActivity(task);
+ final ActivityRecord resultToActivity = taskFragment1.getTopMostActivity();
+ final ActivityRecord targetActivity = taskFragment2.getTopMostActivity();
+ resultToActivity.setState(RESUMED, "test");
+ targetActivity.setState(RESUMED, "test");
+ targetActivity.resultTo = resultToActivity;
+
+ clearInvocations(mAtm.getLifecycleManager());
+ targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
+ waitUntilHandlersIdle();
+
+ verify(resultToActivity).sendResult(anyInt(), eq(null), anyInt(), anyInt(), any(), eq(null),
+ anyBoolean());
+ }
+
/**
* Verify that complete finish request for non-finishing activity is invalid.
*/
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 1575336..8a15c30 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -28,6 +28,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
@@ -275,11 +276,60 @@
// THEN calling intercept returns true
mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
- // THEN the returned intent is the quiet mode intent
+ // THEN the returned intent is the confirm credentials intent
assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
}
@Test
+ public void testLockedManagedProfileShowWhenLocked() {
+ Intent originalIntent = new Intent();
+ // GIVEN that the user is locked but its storage is unlocked and the activity has
+ // showWhenLocked flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(true);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is original intent
+ assertSame(originalIntent, mInterceptor.mIntent);
+ }
+
+ @Test
+ public void testLockedManagedProfileShowWhenLockedEncryptedStorage() {
+ // GIVEN that the user storage is locked, activity has showWhenLocked flag but no
+ // directBootAware flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+ mAInfo.directBootAware = false;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is the confirm credentials intent
+ assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
+ public void testLockedManagedProfileShowWhenLockedEncryptedStorageDirectBootAware() {
+ Intent originalIntent = new Intent();
+ // GIVEN that the user storage is locked, activity has showWhenLocked flag and
+ // directBootAware flag
+ when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
+ when(mUserManager.isUserUnlocked(eq(TEST_USER_ID))).thenReturn(false);
+ mAInfo.flags |= ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+ mAInfo.directBootAware = true;
+
+ // THEN calling intercept returns true
+ mInterceptor.intercept(originalIntent, null, mAInfo, null, null, null, 0, 0, null);
+
+ // THEN the returned intent is original intent
+ assertSame(originalIntent, mInterceptor.mIntent);
+ }
+
+ @Test
public void testHarmfulAppWarning() throws RemoteException {
// GIVEN the package we're about to launch has a harmful app warning set
when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 70b68c7..6733470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -133,8 +133,8 @@
final RoundedCorners roundedCorners = mHasRoundedCorners
? mDisplayContent.calculateRoundedCornersForRotation(mRotation)
: RoundedCorners.NO_ROUNDED_CORNERS;
- return new DisplayFrames(insetsState, info,
- info.displayCutout, roundedCorners, new PrivacyIndicatorBounds());
+ return new DisplayFrames(insetsState, info, info.displayCutout, roundedCorners,
+ new PrivacyIndicatorBounds(), info.displayShape);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index d99946f..10f2270 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -58,6 +58,7 @@
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
@@ -321,7 +322,8 @@
final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
mImeWindow.mAboveInsetsState.set(state);
mDisplayContent.mDisplayFrames = new DisplayFrames(
- state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+ state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+ DisplayShape.NONE);
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
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 4e796c5..8fda191 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -24,7 +24,6 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.os.Process.FIRST_APPLICATION_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -33,9 +32,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
-import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION;
-import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT;
import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -475,23 +472,6 @@
doReturn(true).when(taskFragment).smallerThanMinDimension(any());
assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION,
taskFragment.isAllowedToEmbedActivity(activity));
-
- // Not allow to start activity across TaskFragments for result.
- final TaskFragment newTaskFragment = new TaskFragmentBuilder(mAtm)
- .setParentTask(taskFragment.getTask())
- .build();
- final ActivityRecord newActivity = new ActivityBuilder(mAtm)
- .setUid(FIRST_APPLICATION_UID)
- .build();
- doReturn(true).when(newTaskFragment).isAllowedToEmbedActivityInTrustedMode(any(), anyInt());
- doReturn(false).when(newTaskFragment).smallerThanMinDimension(any());
- newActivity.resultTo = activity;
- assertEquals(EMBEDDING_DISALLOWED_NEW_TASK_FRAGMENT,
- newTaskFragment.isAllowedToEmbedActivity(newActivity));
-
- // Allow embedding if the resultTo activity is finishing.
- activity.finishing = true;
- assertEquals(EMBEDDING_ALLOWED, newTaskFragment.isAllowedToEmbedActivity(newActivity));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 9090c55..94b5b93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -54,6 +54,7 @@
import android.platform.test.annotations.Presubmit;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.DisplayShape;
import android.view.Gravity;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
@@ -165,7 +166,8 @@
final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0);
final DisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
final DisplayFrames displayFrames = new DisplayFrames(new InsetsState(),
- info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
+ info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds(),
+ DisplayShape.NONE);
wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
// Check that the wallpaper has the same frame in landscape than in portrait
@@ -369,7 +371,7 @@
final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
token.finishSync(t, false /* cancel */);
transit.onTransactionReady(transit.getSyncId(), t);
- dc.mTransitionController.finishTransition(transit);
+ dc.mTransitionController.finishTransition(transit.getToken());
assertFalse(wallpaperWindow.isVisible());
assertFalse(token.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 4429aef..871030f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -16,12 +16,16 @@
package com.android.server.wm;
+import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -81,6 +85,9 @@
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
import org.junit.Rule;
import org.junit.Test;
@@ -99,6 +106,11 @@
@Rule
public ExpectedException mExpectedException = ExpectedException.none();
+ @Rule
+ public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ ADD_TRUSTED_DISPLAY);
+
@Test
public void testAddWindowToken() {
IBinder token = mock(IBinder.class);
@@ -396,9 +408,15 @@
@Test
public void testSetInTouchMode_multiDisplay_globalTouchModeUpdate() {
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay();
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
+ final VirtualDisplay virtualDisplayOwnTouchMode =
+ createVirtualDisplay(/* ownFocus= */ true);
final int numberOfDisplays = mWm.mRoot.mChildren.size();
- assertThat(numberOfDisplays).isAtLeast(2);
+ assertThat(numberOfDisplays).isAtLeast(3);
+ final int numberOfGlobalTouchModeDisplays = (int) mWm.mRoot.mChildren.stream()
+ .filter(d -> (d.getDisplay().getFlags() & FLAG_OWN_FOCUS) == 0)
+ .count();
+ assertThat(numberOfGlobalTouchModeDisplays).isAtLeast(2);
// Enable global touch mode (config_perDisplayFocusEnabled set to false)
Resources mockResources = mock(Resources.class);
@@ -417,15 +435,15 @@
mWm.setInTouchMode(!currentTouchMode, DEFAULT_DISPLAY);
- verify(mWm.mInputManager, times(numberOfDisplays)).setInTouchMode(
+ verify(mWm.mInputManager, times(numberOfGlobalTouchModeDisplays)).setInTouchMode(
eq(!currentTouchMode), eq(callingPid), eq(callingUid),
/* hasPermission= */ eq(true), /* displayId= */ anyInt());
}
@Test
- public void testSetInTouchMode_multiDisplay_singleDisplayTouchModeUpdate() {
+ public void testSetInTouchMode_multiDisplay_perDisplayFocus_singleDisplayTouchModeUpdate() {
// Create one extra display
- final VirtualDisplay virtualDisplay = createVirtualDisplay();
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ false);
final int numberOfDisplays = mWm.mRoot.mChildren.size();
assertThat(numberOfDisplays).isAtLeast(2);
@@ -452,14 +470,47 @@
virtualDisplay.getDisplay().getDisplayId());
}
- private VirtualDisplay createVirtualDisplay() {
+ @Test
+ public void testSetInTouchMode_multiDisplay_ownTouchMode_singleDisplayTouchModeUpdate() {
+ // Create one extra display
+ final VirtualDisplay virtualDisplay = createVirtualDisplay(/* ownFocus= */ true);
+ final int numberOfDisplays = mWm.mRoot.mChildren.size();
+ assertThat(numberOfDisplays).isAtLeast(2);
+
+ // Enable global touch mode (config_perDisplayFocusEnabled set to false)
+ Resources mockResources = mock(Resources.class);
+ spyOn(mContext);
+ when(mContext.getResources()).thenReturn(mockResources);
+ doReturn(false).when(mockResources).getBoolean(
+ com.android.internal.R.bool.config_perDisplayFocusEnabled);
+
+ // Get current touch mode state and setup WMS to run setInTouchMode
+ boolean currentTouchMode = mWm.isInTouchMode(DEFAULT_DISPLAY);
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ doReturn(false).when(mWm).checkCallingPermission(anyString(), anyString(), anyBoolean());
+ when(mWm.mAtmService.instrumentationSourceHasPermission(callingPid,
+ android.Manifest.permission.MODIFY_TOUCH_MODE_STATE)).thenReturn(true);
+
+ mWm.setInTouchMode(!currentTouchMode, virtualDisplay.getDisplay().getDisplayId());
+
+ // Ensure that new display touch mode state has changed.
+ verify(mWm.mInputManager).setInTouchMode(
+ !currentTouchMode, callingPid, callingUid, /* hasPermission= */ true,
+ virtualDisplay.getDisplay().getDisplayId());
+ }
+
+ private VirtualDisplay createVirtualDisplay(boolean ownFocus) {
// Create virtual display
Point surfaceSize = new Point(
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().width(),
mDefaultDisplay.getDefaultTaskDisplayArea().getBounds().height());
+ int flags = VIRTUAL_DISPLAY_FLAG_PUBLIC;
+ if (ownFocus) {
+ flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS | VIRTUAL_DISPLAY_FLAG_TRUSTED;
+ }
VirtualDisplay virtualDisplay = mWm.mDisplayManager.createVirtualDisplay("VirtualDisplay",
- surfaceSize.x, surfaceSize.y,
- DisplayMetrics.DENSITY_140, new Surface(), VIRTUAL_DISPLAY_FLAG_PUBLIC);
+ surfaceSize.x, surfaceSize.y, DisplayMetrics.DENSITY_140, new Surface(), flags);
final int displayId = virtualDisplay.getDisplay().getDisplayId();
mWm.mRoot.onDisplayAdded(displayId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 1348770..5a261bc65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -1746,7 +1746,7 @@
}
void startTransition() {
- mOrganizer.startTransition(mLastTransit, null);
+ mOrganizer.startTransition(mLastTransit.getToken(), null);
}
void onTransactionReady(SurfaceControl.Transaction t) {
@@ -1759,7 +1759,7 @@
}
public void finish() {
- mController.finishTransition(mLastTransit);
+ mController.finishTransition(mLastTransit.getToken());
}
}
}
diff --git a/services/usb/Android.bp b/services/usb/Android.bp
index 52cfe25..133f924 100644
--- a/services/usb/Android.bp
+++ b/services/usb/Android.bp
@@ -33,5 +33,6 @@
"android.hardware.usb.gadget-V1.0-java",
"android.hardware.usb.gadget-V1.1-java",
"android.hardware.usb.gadget-V1.2-java",
+ "android.hardware.usb.gadget-V1-java",
],
}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1c081c1..ffdb07b 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -44,6 +44,7 @@
import android.debug.AdbNotifications;
import android.debug.AdbTransportType;
import android.debug.IAdbTransport;
+import android.hardware.usb.IUsbOperationInternal;
import android.hardware.usb.ParcelableUsbPort;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbConfiguration;
@@ -54,9 +55,7 @@
import android.hardware.usb.UsbPort;
import android.hardware.usb.UsbPortStatus;
import android.hardware.usb.gadget.V1_0.GadgetFunction;
-import android.hardware.usb.gadget.V1_0.IUsbGadget;
import android.hardware.usb.gadget.V1_0.Status;
-import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
import android.hardware.usb.gadget.V1_2.UsbSpeed;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
@@ -88,9 +87,12 @@
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.usb.hal.gadget.UsbGadgetHal;
+import com.android.server.usb.hal.gadget.UsbGadgetHalInstance;
import com.android.server.utils.EventLogger;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -106,6 +108,7 @@
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* UsbDeviceManager manages USB state in device mode.
@@ -216,6 +219,13 @@
private static EventLogger sEventLogger;
+ private UsbGadgetHal mUsbGadgetHal;
+
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
static {
sDenyInterfaces = new HashSet<>();
sDenyInterfaces.add(UsbConstants.USB_CLASS_AUDIO);
@@ -298,15 +308,11 @@
mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
initRndisAddress();
+ int operationId = sUsbOperationCount.incrementAndGet();
boolean halNotPresent = false;
- try {
- IUsbGadget.getService(true);
- } catch (RemoteException e) {
- Slog.e(TAG, "USB GADGET HAL present but exception thrown", e);
- } catch (NoSuchElementException e) {
- halNotPresent = true;
- Slog.i(TAG, "USB GADGET HAL not present in the device", e);
- }
+
+ mUsbGadgetHal = UsbGadgetHalInstance.getInstance(this, null);
+ Slog.d(TAG, "getInstance done");
mControlFds = new HashMap<>();
FileDescriptor mtpFd = nativeOpenControl(UsbManager.USB_FUNCTION_MTP);
@@ -320,7 +326,7 @@
}
mControlFds.put(UsbManager.FUNCTION_PTP, ptpFd);
- if (halNotPresent) {
+ if (mUsbGadgetHal == null) {
/**
* Initialze the legacy UsbHandler
*/
@@ -334,6 +340,8 @@
alsaManager, permissionManager);
}
+ mHandler.handlerInitDone(operationId);
+
if (nativeIsStartRequested()) {
if (DEBUG) Slog.d(TAG, "accessory attached at boot");
startAccessoryMode();
@@ -455,6 +463,8 @@
private void startAccessoryMode() {
if (!mHasUsbAccessory) return;
+ int operationId = sUsbOperationCount.incrementAndGet();
+
mAccessoryStrings = nativeGetAccessoryStrings();
boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
// don't start accessory mode if our mandatory strings have not been set
@@ -475,7 +485,7 @@
ACCESSORY_REQUEST_TIMEOUT);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_ACCESSORY_HANDSHAKE_TIMEOUT),
ACCESSORY_HANDSHAKE_TIMEOUT);
- setCurrentFunctions(functions);
+ setCurrentFunctions(functions, operationId);
}
}
@@ -504,6 +514,20 @@
}
}
+ public static void logAndPrint(int priority, IndentingPrintWriter pw, String msg) {
+ Slog.println(priority, TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ }
+ }
+
+ public static void logAndPrintException(IndentingPrintWriter pw, String msg, Exception e) {
+ Slog.e(TAG, msg, e);
+ if (pw != null) {
+ pw.println(msg + e);
+ }
+ }
+
abstract static class UsbHandler extends Handler {
// current USB state
@@ -608,6 +632,19 @@
sendMessage(m);
}
+ public boolean sendMessage(int what) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ return sendMessageDelayed(m,0);
+ }
+
+ public void sendMessage(int what, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, Object arg) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -615,6 +652,22 @@
sendMessage(m);
}
+ public void sendMessage(int what, Object arg, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ m.arg1 = operationId;
+ sendMessage(m);
+ }
+
+ public void sendMessage(int what, boolean arg, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.arg1 = (arg ? 1 : 0);
+ m.arg2 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, Object arg, boolean arg1) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -623,6 +676,15 @@
sendMessage(m);
}
+ public void sendMessage(int what, long arg, boolean arg1, int operationId) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg;
+ m.arg1 = (arg1 ? 1 : 0);
+ m.arg2 = operationId;
+ sendMessage(m);
+ }
+
public void sendMessage(int what, boolean arg1, boolean arg2) {
removeMessages(what);
Message m = Message.obtain(this, what);
@@ -680,7 +742,7 @@
sendMessageDelayed(msg, HOST_STATE_UPDATE_DELAY);
}
- private void setAdbEnabled(boolean enable) {
+ private void setAdbEnabled(boolean enable, int operationId) {
if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
if (enable) {
@@ -689,7 +751,7 @@
setSystemProperty(USB_PERSISTENT_CONFIG_PROPERTY, "");
}
- setEnabledFunctions(mCurrentFunctions, true);
+ setEnabledFunctions(mCurrentFunctions, true, operationId);
updateAdbNotification(false);
}
@@ -701,6 +763,8 @@
private void updateCurrentAccessory() {
// We are entering accessory mode if we have received a request from the host
// and the request has not timed out yet.
+ int operationId = sUsbOperationCount.incrementAndGet();
+
boolean enteringAccessoryMode = hasMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT);
if (mConfigured && enteringAccessoryMode) {
@@ -732,18 +796,18 @@
}
} else {
if (!enteringAccessoryMode) {
- notifyAccessoryModeExit();
+ notifyAccessoryModeExit(operationId);
} else if (DEBUG) {
Slog.v(TAG, "Debouncing accessory mode exit");
}
}
}
- private void notifyAccessoryModeExit() {
+ private void notifyAccessoryModeExit(int operationId) {
// make sure accessory mode is off
// and restore default functions
Slog.d(TAG, "exited USB accessory mode");
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
if (mCurrentAccessory != null) {
if (mBootCompleted) {
@@ -869,8 +933,8 @@
mMidiEnabled && mConfigured, mMidiCard, mMidiDevice);
}
- private void setScreenUnlockedFunctions() {
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ private void setScreenUnlockedFunctions(int operationId) {
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
private static class AdbTransport extends IAdbTransport.Stub {
@@ -883,7 +947,8 @@
@Override
public void onAdbEnabled(boolean enabled, byte transportType) {
if (transportType == AdbTransportType.USB) {
- mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
}
}
}
@@ -906,6 +971,7 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UPDATE_STATE:
+ int operationId = sUsbOperationCount.incrementAndGet();
mConnected = (msg.arg1 == 1);
mConfigured = (msg.arg2 == 1);
@@ -923,9 +989,9 @@
// restore defaults when USB is disconnected
if (!mScreenLocked
&& mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
}
updateUsbFunctions();
@@ -1036,13 +1102,15 @@
updateUsbNotification(false);
break;
case MSG_ENABLE_ADB:
- setAdbEnabled(msg.arg1 == 1);
+ setAdbEnabled(msg.arg1 == 1, msg.arg2);
break;
case MSG_SET_CURRENT_FUNCTIONS:
long functions = (Long) msg.obj;
- setEnabledFunctions(functions, false);
+ operationId = (int) msg.arg1;
+ setEnabledFunctions(functions, false, operationId);
break;
case MSG_SET_SCREEN_UNLOCKED_FUNCTIONS:
+ operationId = sUsbOperationCount.incrementAndGet();
mScreenUnlockedFunctions = (Long) msg.obj;
if (mSettings != null) {
SharedPreferences.Editor editor = mSettings.edit();
@@ -1053,12 +1121,13 @@
}
if (!mScreenLocked && mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
// If the screen is unlocked, also set current functions.
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
break;
case MSG_UPDATE_SCREEN_LOCK:
+ operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 == 1 == mScreenLocked) {
break;
}
@@ -1068,23 +1137,25 @@
}
if (mScreenLocked) {
if (!mConnected) {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
} else {
if (mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE
&& mCurrentFunctions == UsbManager.FUNCTION_NONE) {
// Set the screen unlocked functions if current function is charging.
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
}
}
break;
case MSG_UPDATE_USER_RESTRICTIONS:
+ operationId = sUsbOperationCount.incrementAndGet();
// Restart the USB stack if USB transfer is enabled but no longer allowed.
if (isUsbDataTransferActive(mCurrentFunctions) && !isUsbTransferAllowed()) {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, true);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, true, operationId);
}
break;
case MSG_SYSTEM_READY:
+ operationId = sUsbOperationCount.incrementAndGet();
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
@@ -1102,17 +1173,19 @@
NotificationManager.IMPORTANCE_HIGH));
}
mSystemReady = true;
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_LOCALE_CHANGED:
updateAdbNotification(true);
updateUsbNotification(true);
break;
case MSG_BOOT_COMPLETED:
+ operationId = sUsbOperationCount.incrementAndGet();
mBootCompleted = true;
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_USER_SWITCHED: {
+ operationId = sUsbOperationCount.incrementAndGet();
if (mCurrentUser != msg.arg1) {
if (DEBUG) {
Slog.v(TAG, "Current user switched to " + msg.arg1);
@@ -1125,16 +1198,18 @@
mSettings.getString(String.format(Locale.ENGLISH,
UNLOCKED_CONFIG_PREF, mCurrentUser), ""));
}
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
break;
}
case MSG_ACCESSORY_MODE_ENTER_TIMEOUT: {
+ operationId = sUsbOperationCount.incrementAndGet();
if (DEBUG) {
- Slog.v(TAG, "Accessory mode enter timeout: " + mConnected);
+ Slog.v(TAG, "Accessory mode enter timeout: " + mConnected
+ + " ,operationId: " + operationId);
}
if (!mConnected || (mCurrentFunctions & UsbManager.FUNCTION_ACCESSORY) == 0) {
- notifyAccessoryModeExit();
+ notifyAccessoryModeExit(operationId);
}
break;
}
@@ -1157,7 +1232,9 @@
}
}
- protected void finishBoot() {
+ public abstract void handlerInitDone(int operationId);
+
+ protected void finishBoot(int operationId) {
if (mBootCompleted && mCurrentUsbFunctionsReceived && mSystemReady) {
if (mPendingBootBroadcast) {
updateUsbStateBroadcastIfNeeded(getAppliedFunctions(mCurrentFunctions));
@@ -1165,9 +1242,9 @@
}
if (!mScreenLocked
&& mScreenUnlockedFunctions != UsbManager.FUNCTION_NONE) {
- setScreenUnlockedFunctions();
+ setScreenUnlockedFunctions(operationId);
} else {
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
}
if (mCurrentAccessory != null) {
mUsbDeviceManager.getCurrentSettings().accessoryAttached(mCurrentAccessory);
@@ -1507,7 +1584,8 @@
/**
* Evaluates USB function policies and applies the change accordingly.
*/
- protected abstract void setEnabledFunctions(long functions, boolean forceRestart);
+ protected abstract void setEnabledFunctions(long functions,
+ boolean forceRestart, int operationId);
public void setAccessoryUEventTime(long accessoryConnectionStartTime) {
mAccessoryConnectionStartTime = accessoryConnectionStartTime;
@@ -1522,6 +1600,11 @@
mSendStringCount = 0;
mStartAccessory = false;
}
+
+ public abstract void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions);
+
+ public abstract void getUsbSpeedCb(int speed);
}
private static final class UsbHandlerLegacy extends UsbHandler {
@@ -1540,6 +1623,11 @@
private String mCurrentFunctionsStr;
private boolean mUsbDataUnlocked;
+ /**
+ * Keeps track of the latest setCurrentUsbFunctions request number.
+ */
+ private int mCurrentRequest = 0;
+
UsbHandlerLegacy(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
super(looper, context, deviceManager, alsaManager, permissionManager);
@@ -1573,6 +1661,10 @@
}
}
+ @Override
+ public void handlerInitDone(int operationId) {
+ }
+
private void readOemUsbOverrideConfig(Context context) {
String[] configList = context.getResources().getStringArray(
com.android.internal.R.array.config_oemUsbModeOverride);
@@ -1675,11 +1767,14 @@
}
@Override
- protected void setEnabledFunctions(long usbFunctions, boolean forceRestart) {
+ protected void setEnabledFunctions(long usbFunctions,
+ boolean forceRestart, int operationId) {
boolean usbDataUnlocked = isUsbDataTransferActive(usbFunctions);
if (DEBUG) {
- Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions + ", "
- + "forceRestart=" + forceRestart + ", usbDataUnlocked=" + usbDataUnlocked);
+ Slog.d(TAG, "setEnabledFunctions functions=" + usbFunctions +
+ " ,forceRestart=" + forceRestart +
+ " ,usbDataUnlocked=" + usbDataUnlocked +
+ " ,operationId=" + operationId);
}
if (usbDataUnlocked != mUsbDataUnlocked) {
@@ -1775,7 +1870,6 @@
|| !mCurrentFunctionsStr.equals(functions)
|| !mCurrentFunctionsApplied
|| forceRestart) {
- Slog.i(TAG, "Setting USB config to " + functions);
mCurrentFunctionsStr = functions;
mCurrentOemFunctions = oemFunctions;
mCurrentFunctionsApplied = false;
@@ -1871,15 +1965,18 @@
if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
return true;
}
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed){
+ }
}
- private static final class UsbHandlerHal extends UsbHandler {
-
- /**
- * Proxy object for the usb gadget hal daemon.
- */
- @GuardedBy("mGadgetProxyLock")
- private IUsbGadget mGadgetProxy;
+ private final class UsbHandlerHal extends UsbHandler {
private final Object mGadgetProxyLock = new Object();
@@ -1926,33 +2023,20 @@
UsbHandlerHal(Looper looper, Context context, UsbDeviceManager deviceManager,
UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) {
super(looper, context, deviceManager, alsaManager, permissionManager);
+ int operationId = sUsbOperationCount.incrementAndGet();
try {
- ServiceNotification serviceNotification = new ServiceNotification();
-
- boolean ret = IServiceManager.getService()
- .registerForNotifications(GADGET_HAL_FQ_NAME, "", serviceNotification);
- if (!ret) {
- Slog.e(TAG, "Failed to register usb gadget service start notification");
- return;
- }
synchronized (mGadgetProxyLock) {
- mGadgetProxy = IUsbGadget.getService(true);
- mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
- USB_GADGET_HAL_DEATH_COOKIE);
mCurrentFunctions = UsbManager.FUNCTION_NONE;
mCurrentUsbFunctionsRequested = true;
mUsbSpeed = UsbSpeed.UNKNOWN;
mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
- mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+ updateUsbGadgetHalVersion();
}
String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
updateState(state);
- updateUsbGadgetHalVersion();
} catch (NoSuchElementException e) {
Slog.e(TAG, "Usb gadget hal not found", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Usb Gadget hal not responding", e);
} catch (Exception e) {
Slog.e(TAG, "Error initializing UsbHandler", e);
}
@@ -1965,7 +2049,7 @@
if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
Slog.e(TAG, "Usb Gadget hal service died cookie: " + cookie);
synchronized (mGadgetProxyLock) {
- mGadgetProxy = null;
+ mUsbGadgetHal = null;
}
}
}
@@ -1988,18 +2072,22 @@
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SET_CHARGING_FUNCTIONS:
- setEnabledFunctions(UsbManager.FUNCTION_NONE, false);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ setEnabledFunctions(UsbManager.FUNCTION_NONE, false, operationId);
break;
case MSG_SET_FUNCTIONS_TIMEOUT:
- Slog.e(TAG, "Set functions timed out! no reply from usb hal");
+ operationId = sUsbOperationCount.incrementAndGet();
+ Slog.e(TAG, "Set functions timed out! no reply from usb hal"
+ + " ,operationId:" + operationId);
if (msg.arg1 != 1) {
// Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
break;
case MSG_GET_CURRENT_USB_FUNCTIONS:
Slog.i(TAG, "processing MSG_GET_CURRENT_USB_FUNCTIONS");
mCurrentUsbFunctionsReceived = true;
+ operationId = msg.arg2;
if (mCurrentUsbFunctionsRequested) {
Slog.i(TAG, "updating mCurrentFunctions");
@@ -2009,91 +2097,71 @@
"mCurrentFunctions:" + mCurrentFunctions + "applied:" + msg.arg1);
mCurrentFunctionsApplied = msg.arg1 == 1;
}
- finishBoot();
+ finishBoot(operationId);
break;
case MSG_FUNCTION_SWITCH_TIMEOUT:
/**
* Dont force to default when the configuration is already set to default.
*/
+ operationId = sUsbOperationCount.incrementAndGet();
if (msg.arg1 != 1) {
// Set this since default function may be selected from Developer options
- setEnabledFunctions(mScreenUnlockedFunctions, false);
+ setEnabledFunctions(mScreenUnlockedFunctions, false, operationId);
}
break;
case MSG_GADGET_HAL_REGISTERED:
boolean preexisting = msg.arg1 == 1;
+ operationId = sUsbOperationCount.incrementAndGet();
synchronized (mGadgetProxyLock) {
try {
- mGadgetProxy = IUsbGadget.getService();
- mGadgetProxy.linkToDeath(new UsbGadgetDeathRecipient(),
- USB_GADGET_HAL_DEATH_COOKIE);
+ mUsbGadgetHal = UsbGadgetHalInstance.getInstance(mUsbDeviceManager,
+ null);
if (!mCurrentFunctionsApplied && !preexisting) {
- setEnabledFunctions(mCurrentFunctions, false);
+ setEnabledFunctions(mCurrentFunctions, false, operationId);
}
} catch (NoSuchElementException e) {
Slog.e(TAG, "Usb gadget hal not found", e);
- } catch (RemoteException e) {
- Slog.e(TAG, "Usb Gadget hal not responding", e);
}
}
break;
case MSG_RESET_USB_GADGET:
synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "reset Usb Gadget mGadgetProxy is null");
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "reset Usb Gadget mUsbGadgetHal is null");
break;
}
try {
- android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_1.IUsbGadget
- .castFrom(mGadgetProxy);
- gadgetProxy.reset();
- } catch (RemoteException e) {
+ mUsbGadgetHal.reset();
+ } catch (Exception e) {
Slog.e(TAG, "reset Usb Gadget failed", e);
}
}
break;
case MSG_UPDATE_USB_SPEED:
- synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "mGadgetProxy is null");
- break;
- }
+ operationId = sUsbOperationCount.incrementAndGet();
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "mGadgetHal is null, operationId:" + operationId);
+ break;
+ }
- try {
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget
- .castFrom(mGadgetProxy);
- if (gadgetProxy != null) {
- gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "get UsbSpeed failed", e);
- }
+ try {
+ mUsbGadgetHal.getUsbSpeed(operationId);
+ } catch (Exception e) {
+ Slog.e(TAG, "get UsbSpeed failed", e);
}
break;
case MSG_UPDATE_HAL_VERSION:
- synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "mGadgetProxy is null");
- break;
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "mUsbGadgetHal is null");
+ break;
+ }
+ else {
+ try {
+ mCurrentGadgetHalVersion = mUsbGadgetHal.getGadgetHalVersion();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "update Usb gadget version failed", e);
}
-
- android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
- android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
- if (gadgetProxy == null) {
- android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxyV1By1 =
- android.hardware.usb.gadget.V1_1.IUsbGadget
- .castFrom(mGadgetProxy);
- if (gadgetProxyV1By1 == null) {
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_0;
- break;
- }
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_1;
- break;
- }
- mCurrentGadgetHalVersion = UsbManager.GADGET_HAL_V1_2;
}
break;
default:
@@ -2101,56 +2169,31 @@
}
}
- private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
- int mRequest;
- long mFunctions;
- boolean mChargingFunctions;
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
- UsbGadgetCallback() {
+ if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
+ || (mFunctions != functions)) {
+ return;
}
- UsbGadgetCallback(int request, long functions,
- boolean chargingFunctions) {
- mRequest = request;
- mFunctions = functions;
- mChargingFunctions = chargingFunctions;
- }
-
- @Override
- public void setCurrentUsbFunctionsCb(long functions,
- int status) {
- /**
- * Callback called for a previous setCurrenUsbFunction
- */
- if ((mCurrentRequest != mRequest) || !hasMessages(MSG_SET_FUNCTIONS_TIMEOUT)
- || (mFunctions != functions)) {
- return;
- }
-
- removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
- Slog.e(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
- if (status == Status.SUCCESS) {
- mCurrentFunctionsApplied = true;
- } else if (!mChargingFunctions) {
- Slog.e(TAG, "Setting default fuctions");
- sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
- }
- }
-
- @Override
- public void getCurrentUsbFunctionsCb(long functions,
- int status) {
- sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
- status == Status.FUNCTIONS_APPLIED);
- }
-
- @Override
- public void getUsbSpeedCb(int speed) {
- mUsbSpeed = speed;
+ removeMessages(MSG_SET_FUNCTIONS_TIMEOUT);
+ Slog.i(TAG, "notifyCurrentFunction request:" + mRequest + " status:" + status);
+ if (status == Status.SUCCESS) {
+ mCurrentFunctionsApplied = true;
+ } else if (!mChargingFunctions) {
+ Slog.e(TAG, "Setting default fuctions");
+ sendEmptyMessage(MSG_SET_CHARGING_FUNCTIONS);
}
}
- private void setUsbConfig(long config, boolean chargingFunctions) {
+ @Override
+ public void getUsbSpeedCb(int speed) {
+ mUsbSpeed = speed;
+ }
+
+ private void setUsbConfig(long config, boolean chargingFunctions, int operationId) {
if (true) Slog.d(TAG, "setUsbConfig(" + config + ") request:" + ++mCurrentRequest);
/**
* Cancel any ongoing requests, if present.
@@ -2160,8 +2203,8 @@
removeMessages(MSG_SET_CHARGING_FUNCTIONS);
synchronized (mGadgetProxyLock) {
- if (mGadgetProxy == null) {
- Slog.e(TAG, "setUsbConfig mGadgetProxy is null");
+ if (mUsbGadgetHal == null) {
+ Slog.e(TAG, "setUsbConfig mUsbGadgetHal is null");
return;
}
try {
@@ -2178,10 +2221,9 @@
LocalServices.getService(AdbManagerInternal.class)
.stopAdbdForTransport(AdbTransportType.USB);
}
- UsbGadgetCallback usbGadgetCallback = new UsbGadgetCallback(mCurrentRequest,
- config, chargingFunctions);
- mGadgetProxy.setCurrentUsbFunctions(config, usbGadgetCallback,
- SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS);
+ mUsbGadgetHal.setCurrentUsbFunctions(mCurrentRequest,
+ config, chargingFunctions,
+ SET_FUNCTIONS_TIMEOUT_MS - SET_FUNCTIONS_LEEWAY_MS, operationId);
sendMessageDelayed(MSG_SET_FUNCTIONS_TIMEOUT, chargingFunctions,
SET_FUNCTIONS_TIMEOUT_MS);
if (mConnected) {
@@ -2190,17 +2232,19 @@
SET_FUNCTIONS_TIMEOUT_MS + ENUMERATION_TIME_OUT_MS);
}
if (DEBUG) Slog.d(TAG, "timeout message queued");
- } catch (RemoteException e) {
+ } catch (Exception e) {//RemoteException e) {
Slog.e(TAG, "Remoteexception while calling setCurrentUsbFunctions", e);
}
}
}
@Override
- protected void setEnabledFunctions(long functions, boolean forceRestart) {
+ protected void setEnabledFunctions(long functions, boolean forceRestart, int operationId) {
if (DEBUG) {
- Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", "
- + "forceRestart=" + forceRestart);
+ Slog.d(TAG, "setEnabledFunctionsi " +
+ "functions=" + functions +
+ ", forceRestart=" + forceRestart +
+ ", operationId=" + operationId);
}
if (mCurrentGadgetHalVersion < UsbManager.GADGET_HAL_V1_2) {
if ((functions & UsbManager.FUNCTION_NCM) != 0) {
@@ -2221,7 +2265,7 @@
functions = getAppliedFunctions(functions);
// Set the new USB configuration.
- setUsbConfig(functions, chargingFunctions);
+ setUsbConfig(functions, chargingFunctions, operationId);
if (mBootCompleted && isUsbDataTransferActive(functions)) {
// Start up dependent services.
@@ -2229,6 +2273,11 @@
}
}
}
+
+ @Override
+ public void handlerInitDone(int operationId) {
+ mUsbGadgetHal.getCurrentUsbFunctions(operationId);
+ }
}
/* returns the currently attached USB accessory */
@@ -2270,6 +2319,21 @@
return mHandler.getGadgetHalVersion();
}
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions) {
+ mHandler.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ public void getCurrentUsbFunctionsCb(long functions, int status) {
+ mHandler.sendMessage(MSG_GET_CURRENT_USB_FUNCTIONS, functions,
+ status == Status.FUNCTIONS_APPLIED);
+ }
+
+ public void getUsbSpeedCb(int speed) {
+ mHandler.getUsbSpeedCb(speed);
+ }
+
/**
* Returns a dup of the control file descriptor for the given function.
*/
@@ -2295,7 +2359,7 @@
*
* @param functions The functions to set, or empty to set the charging function.
*/
- public void setCurrentFunctions(long functions) {
+ public void setCurrentFunctions(long functions, int operationId) {
if (DEBUG) {
Slog.d(TAG, "setCurrentFunctions(" + UsbManager.usbFunctionsToString(functions) + ")");
}
@@ -2312,7 +2376,7 @@
} else if (functions == UsbManager.FUNCTION_ACCESSORY) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_USB_CONFIG_ACCESSORY);
}
- mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions);
+ mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, operationId);
}
/**
@@ -2340,7 +2404,8 @@
}
private void onAdbEnabled(boolean enabled) {
- mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+ int operationId = sUsbOperationCount.incrementAndGet();
+ mHandler.sendMessage(MSG_ENABLE_ADB, enabled, operationId);
}
/**
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index d821dee..d09f729 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/com/android/server/usb/UsbService.java
@@ -622,16 +622,16 @@
}
@Override
- public void setCurrentFunctions(long functions) {
+ public void setCurrentFunctions(long functions, int operationId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
Preconditions.checkArgument(UsbManager.areSettableFunctions(functions));
Preconditions.checkState(mDeviceManager != null);
- mDeviceManager.setCurrentFunctions(functions);
+ mDeviceManager.setCurrentFunctions(functions, operationId);
}
@Override
- public void setCurrentFunction(String functions, boolean usbDataUnlocked) {
- setCurrentFunctions(UsbManager.usbFunctionsFromString(functions));
+ public void setCurrentFunction(String functions, boolean usbDataUnlocked, int operationId) {
+ setCurrentFunctions(UsbManager.usbFunctionsFromString(functions), operationId);
}
@Override
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
new file mode 100644
index 0000000..bdfe60a
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetAidl.java
@@ -0,0 +1,220 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_V2_0;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.IUsbGadget;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.os.ServiceManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+/**
+ * Implements the methods to interact with AIDL USB HAL.
+ */
+public final class UsbGadgetAidl implements UsbGadgetHal {
+ private static final String TAG = UsbGadgetAidl.class.getSimpleName();
+ private static final String USB_GADGET_AIDL_SERVICE = IUsbGadget.DESCRIPTOR + "/default";
+ // Proxy object for the usb gadget hal daemon.
+ @GuardedBy("mGadgetProxyLock")
+ private IUsbGadget mGadgetProxy;
+ private final UsbDeviceManager mDeviceManager;
+ public final IndentingPrintWriter mPw;
+ // Mutex for all mutable shared state.
+ private final Object mGadgetProxyLock = new Object();
+ // Callback when the UsbDevice status is changed by the kernel.
+ private UsbGadgetCallback mUsbGadgetCallback;
+
+ public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy == null) {
+ throw new RemoteException("IUsb not initialized yet");
+ }
+ }
+ Slog.i(TAG, "USB Gadget HAL AIDL version: GADGET_HAL_V2_0");
+ return GADGET_HAL_V2_0;
+ }
+
+ @Override
+ public void systemReady() {
+ }
+
+ public void serviceDied() {
+ logAndPrint(Log.ERROR, mPw, "Usb Gadget AIDL hal service died");
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = null;
+ }
+ connectToProxy(null);
+ }
+
+ private void connectToProxy(IndentingPrintWriter pw) {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy != null) {
+ return;
+ }
+
+ try {
+ mGadgetProxy = IUsbGadget.Stub.asInterface(
+ ServiceManager.waitForService(USB_GADGET_AIDL_SERVICE));
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+ + " Did the service fail to start?", e);
+ }
+ }
+ }
+
+ static boolean isServicePresent(IndentingPrintWriter pw) {
+ try {
+ return ServiceManager.isDeclared(USB_GADGET_AIDL_SERVICE);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget Aidl hal service not found.", e);
+ }
+
+ return false;
+ }
+
+ public UsbGadgetAidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+ mDeviceManager = Objects.requireNonNull(deviceManager);
+ mPw = pw;
+ connectToProxy(mPw);
+ }
+
+ @Override
+ public void getCurrentUsbFunctions(long operationId) {
+ synchronized (mGadgetProxyLock) {
+ try {
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback(), operationId);
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getCurrentUsbFunctions"
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void getUsbSpeed(long operationId) {
+ try {
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.getUsbSpeed(new UsbGadgetCallback(), operationId);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed"
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+
+ @Override
+ public void reset() {
+ try {
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.reset();
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed", e);
+ return;
+ }
+ }
+
+ @Override
+ public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+ boolean mChargingFunctions, int timeout, long operationId) {
+ try {
+ mUsbGadgetCallback = new UsbGadgetCallback(mRequest,
+ mFunctions, mChargingFunctions);
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback,
+ timeout, operationId);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling setCurrentUsbFunctions: "
+ + "mRequest=" + mRequest
+ + ", mFunctions=" + mFunctions
+ + ", mChargingFunctions=" + mChargingFunctions
+ + ", timeout=" + timeout
+ + ", opID:" + operationId, e);
+ return;
+ }
+ }
+
+ private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+ public int mRequest;
+ public long mFunctions;
+ public boolean mChargingFunctions;
+
+ UsbGadgetCallback() {
+ }
+
+ UsbGadgetCallback(int request, long functions,
+ boolean chargingFunctions) {
+ mRequest = request;
+ mFunctions = functions;
+ mChargingFunctions = chargingFunctions;
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, long transactionId) {
+ mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ @Override
+ public void getCurrentUsbFunctionsCb(long functions,
+ int status, long transactionId) {
+ mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed, long transactionId) {
+ mDeviceManager.getUsbSpeedCb(speed);
+ }
+
+ @Override
+ public String getInterfaceHash() {
+ return IUsbGadgetCallback.HASH;
+ }
+
+ @Override
+ public int getInterfaceVersion() {
+ return IUsbGadgetCallback.VERSION;
+ }
+ }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
new file mode 100644
index 0000000..267247b
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHal.java
@@ -0,0 +1,138 @@
+/*
+ * 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.usb.hal.gadget;
+
+import android.annotation.IntDef;
+import android.hardware.usb.gadget.IUsbGadgetCallback;
+import android.hardware.usb.IUsbOperationInternal;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.os.RemoteException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.String;
+
+/**
+ * @hide
+ */
+public interface UsbGadgetHal {
+ /**
+ * Power role: This USB port can act as a source (provide power).
+ * @hide
+ */
+ public static final int HAL_POWER_ROLE_SOURCE = 1;
+
+ /**
+ * Power role: This USB port can act as a sink (receive power).
+ * @hide
+ */
+ public static final int HAL_POWER_ROLE_SINK = 2;
+
+ @IntDef(prefix = { "HAL_POWER_ROLE_" }, value = {
+ HAL_POWER_ROLE_SOURCE,
+ HAL_POWER_ROLE_SINK
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbPowerRole{}
+
+ /**
+ * Data role: This USB port can act as a host (access data services).
+ * @hide
+ */
+ public static final int HAL_DATA_ROLE_HOST = 1;
+
+ /**
+ * Data role: This USB port can act as a device (offer data services).
+ * @hide
+ */
+ public static final int HAL_DATA_ROLE_DEVICE = 2;
+
+ @IntDef(prefix = { "HAL_DATA_ROLE_" }, value = {
+ HAL_DATA_ROLE_HOST,
+ HAL_DATA_ROLE_DEVICE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbDataRole{}
+
+ /**
+ * This USB port can act as a downstream facing port (host).
+ *
+ * @hide
+ */
+ public static final int HAL_MODE_DFP = 1;
+
+ /**
+ * This USB port can act as an upstream facing port (device).
+ *
+ * @hide
+ */
+ public static final int HAL_MODE_UFP = 2;
+ @IntDef(prefix = { "HAL_MODE_" }, value = {
+ HAL_MODE_DFP,
+ HAL_MODE_UFP,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface HalUsbPortMode{}
+
+ /**
+ * UsbPortManager would call this when the system is done booting.
+ */
+ public void systemReady();
+
+ /**
+ * This function is used to query the USB functions included in the
+ * current USB configuration.
+ *
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void getCurrentUsbFunctions(long transactionId);
+
+ /**
+ * The function is used to query current USB speed.
+ *
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void getUsbSpeed(long transactionId);
+
+ /**
+ * This function is used to reset USB gadget driver.
+ * Performs USB data connection reset. The connection will disconnect and
+ * reconnect.
+ */
+ public void reset();
+
+ /**
+ * Invoked to query the version of current gadget hal implementation.
+ */
+ public @UsbHalVersion int getGadgetHalVersion() throws RemoteException;
+
+ /**
+ * This function is used to set the current USB gadget configuration.
+ * The USB gadget needs to be torn down if a USB configuration is already
+ * active.
+ *
+ * @param functions list of functions defined by GadgetFunction to be
+ * included in the gadget composition.
+ * @param timeout The maximum time (in milliseconds) within which the
+ * IUsbGadgetCallback needs to be returned.
+ * @param transactionId Used for tracking the current request and is passed down to the HAL
+ * implementation as needed.
+ */
+ public void setCurrentUsbFunctions(int request, long functions,
+ boolean chargingFunctions, int timeout, long transactionId);
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
new file mode 100644
index 0000000..d268315
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHalInstance.java
@@ -0,0 +1,48 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static com.android.server.usb.UsbPortManager.logAndPrint;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.hal.gadget.UsbGadgetHidl;
+import com.android.server.usb.hal.gadget.UsbGadgetAidl;
+import com.android.server.usb.UsbDeviceManager;
+
+import android.util.Log;
+/**
+ * Helper class that queries the underlying hal layer to populate UsbPortHal instance.
+ */
+public final class UsbGadgetHalInstance {
+
+ public static UsbGadgetHal getInstance(UsbDeviceManager deviceManager,
+ IndentingPrintWriter pw) {
+
+ logAndPrint(Log.DEBUG, pw, "Querying USB Gadget HAL version");
+ if (UsbGadgetAidl.isServicePresent(null)) {
+ logAndPrint(Log.INFO, pw, "USB Gadget HAL AIDL present");
+ return new UsbGadgetAidl(deviceManager, pw);
+ }
+ if (UsbGadgetHidl.isServicePresent(null)) {
+ logAndPrint(Log.INFO, pw, "USB Gadget HAL HIDL present");
+ return new UsbGadgetHidl(deviceManager, pw);
+ }
+
+ logAndPrint(Log.ERROR, pw, "USB Gadget HAL AIDL/HIDL not present");
+ return null;
+ }
+}
+
diff --git a/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
new file mode 100644
index 0000000..3e5ecc5
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/hal/gadget/UsbGadgetHidl.java
@@ -0,0 +1,261 @@
+/*
+ * 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.usb.hal.gadget;
+
+import static android.hardware.usb.UsbManager.GADGET_HAL_NOT_SUPPORTED;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_0;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_1;
+import static android.hardware.usb.UsbManager.GADGET_HAL_V1_2;
+
+import static com.android.server.usb.UsbDeviceManager.logAndPrint;
+import static com.android.server.usb.UsbDeviceManager.logAndPrintException;
+
+import android.annotation.Nullable;
+import android.hardware.usb.gadget.V1_0.Status;
+import android.hardware.usb.gadget.V1_0.IUsbGadget;
+import android.hardware.usb.gadget.V1_2.IUsbGadgetCallback;
+import android.hardware.usb.gadget.V1_2.UsbSpeed;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbManager.UsbGadgetHalVersion;
+import android.hardware.usb.UsbManager.UsbHalVersion;
+import android.hidl.manager.V1_0.IServiceManager;
+import android.hidl.manager.V1_0.IServiceNotification;
+import android.os.IHwBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.usb.UsbDeviceManager;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+/**
+ *
+ */
+public final class UsbGadgetHidl implements UsbGadgetHal {
+ // Cookie sent for usb gadget hal death notification.
+ private static final int USB_GADGET_HAL_DEATH_COOKIE = 2000;
+ // Proxy object for the usb gadget hal daemon.
+ @GuardedBy("mGadgetProxyLock")
+ private IUsbGadget mGadgetProxy;
+ private UsbDeviceManager mDeviceManager;
+ private final IndentingPrintWriter mPw;
+ // Mutex for all mutable shared state.
+ private final Object mGadgetProxyLock = new Object();
+ private UsbGadgetCallback mUsbGadgetCallback;
+
+ public @UsbGadgetHalVersion int getGadgetHalVersion() throws RemoteException {
+ int version;
+ synchronized(mGadgetProxyLock) {
+ if (mGadgetProxy == null) {
+ throw new RemoteException("IUsbGadget not initialized yet");
+ }
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ version = UsbManager.GADGET_HAL_V1_2;
+ } else if (android.hardware.usb.gadget.V1_1.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ version = UsbManager.GADGET_HAL_V1_1;
+ } else {
+ version = UsbManager.GADGET_HAL_V1_0;
+ }
+ logAndPrint(Log.INFO, mPw, "USB Gadget HAL HIDL version: " + version);
+ return version;
+ }
+ }
+
+ final class DeathRecipient implements IHwBinder.DeathRecipient {
+ private final IndentingPrintWriter mPw;
+
+ DeathRecipient(IndentingPrintWriter pw) {
+ mPw = pw;
+ }
+
+ @Override
+ public void serviceDied(long cookie) {
+ if (cookie == USB_GADGET_HAL_DEATH_COOKIE) {
+ logAndPrint(Log.ERROR, mPw, "Usb Gadget hal service died cookie: " + cookie);
+ synchronized (mGadgetProxyLock) {
+ mGadgetProxy = null;
+ }
+ }
+ }
+ }
+
+ final class ServiceNotification extends IServiceNotification.Stub {
+ @Override
+ public void onRegistration(String fqName, String name, boolean preexisting) {
+ logAndPrint(Log.INFO, mPw, "Usb gadget hal service started " + fqName + " " + name);
+ connectToProxy(null);
+ }
+ }
+
+ private void connectToProxy(IndentingPrintWriter pw) {
+ synchronized (mGadgetProxyLock) {
+ if (mGadgetProxy != null) {
+ return;
+ }
+
+ try {
+ mGadgetProxy = IUsbGadget.getService();
+ mGadgetProxy.linkToDeath(new DeathRecipient(pw), USB_GADGET_HAL_DEATH_COOKIE);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not found."
+ + " Did the service fail to start?", e);
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hal service not responding"
+ , e);
+ }
+ }
+ }
+
+ @Override
+ public void systemReady() {
+ }
+
+ static boolean isServicePresent(IndentingPrintWriter pw) {
+ try {
+ IUsbGadget.getService(true);
+ } catch (NoSuchElementException e) {
+ logAndPrintException(pw, "connectToProxy: usb gadget hidl hal service not found.", e);
+ return false;
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "IUSBGadget hal service present but failed to get service", e);
+ }
+
+ return true;
+ }
+
+ public UsbGadgetHidl(UsbDeviceManager deviceManager, IndentingPrintWriter pw) {
+ mDeviceManager = Objects.requireNonNull(deviceManager);
+ mPw = pw;
+ try {
+ ServiceNotification serviceNotification = new ServiceNotification();
+
+ boolean ret = IServiceManager.getService()
+ .registerForNotifications("android.hardware.usb.gadget@1.0::IUsbGadget",
+ "", serviceNotification);
+ if (!ret) {
+ logAndPrint(Log.ERROR, pw, "Failed to register service start notification");
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(pw, "Failed to register service start notification", e);
+ return;
+ }
+ connectToProxy(mPw);
+ }
+
+ @Override
+ public void getCurrentUsbFunctions(long transactionId) {
+ try {
+ synchronized(mGadgetProxyLock) {
+ mGadgetProxy.getCurrentUsbFunctions(new UsbGadgetCallback());
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getCurrentUsbFunctions", e);
+ return;
+ }
+ }
+
+ @Override
+ public void getUsbSpeed(long transactionId) {
+ try {
+ synchronized(mGadgetProxyLock) {
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ gadgetProxy.getUsbSpeed(new UsbGadgetCallback());
+ }
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw, "get UsbSpeed failed", e);
+ }
+ }
+
+ @Override
+ public void reset() {
+ try {
+ synchronized(mGadgetProxyLock) {
+ if (android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy) != null) {
+ android.hardware.usb.gadget.V1_2.IUsbGadget gadgetProxy =
+ android.hardware.usb.gadget.V1_2.IUsbGadget.castFrom(mGadgetProxy);
+ gadgetProxy.reset();
+ }
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling getUsbSpeed", e);
+ return;
+ }
+ }
+
+ @Override
+ public void setCurrentUsbFunctions(int mRequest, long mFunctions,
+ boolean mChargingFunctions, int timeout, long operationId) {
+ try {
+ mUsbGadgetCallback = new UsbGadgetCallback(null, mRequest,
+ mFunctions, mChargingFunctions);
+ synchronized(mGadgetProxyLock) {
+ mGadgetProxy.setCurrentUsbFunctions(mFunctions, mUsbGadgetCallback, timeout);
+ }
+ } catch (RemoteException e) {
+ logAndPrintException(mPw,
+ "RemoteException while calling setCurrentUsbFunctions"
+ + " mRequest = " + mRequest
+ + ", mFunctions = " + mFunctions
+ + ", timeout = " + timeout
+ + ", mChargingFunctions = " + mChargingFunctions
+ + ", operationId =" + operationId, e);
+ return;
+ }
+ }
+
+ private class UsbGadgetCallback extends IUsbGadgetCallback.Stub {
+ public int mRequest;
+ public long mFunctions;
+ public boolean mChargingFunctions;
+
+ UsbGadgetCallback() {
+ }
+ UsbGadgetCallback(IndentingPrintWriter pw, int request,
+ long functions, boolean chargingFunctions) {
+ mRequest = request;
+ mFunctions = functions;
+ mChargingFunctions = chargingFunctions;
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status) {
+ mDeviceManager.setCurrentUsbFunctionsCb(functions, status,
+ mRequest, mFunctions, mChargingFunctions);
+ }
+
+ @Override
+ public void getCurrentUsbFunctionsCb(long functions,
+ int status) {
+ mDeviceManager.getCurrentUsbFunctionsCb(functions, status);
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed) {
+ mDeviceManager.getUsbSpeedCb(speed);
+ }
+ }
+}
+
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 5cb0c17..1d3b6481 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -100,56 +100,60 @@
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
std::vector<dex::MethodBuilder> methods;
- assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
- android::FileType) {
- if (s == "layout") {
- auto path = StringPrintf("res/%s/", s.to_string().c_str());
- assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
- android::FileType) {
- auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
- android::ApkAssetsCookie cookie = android::kInvalidCookie;
- auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
- CHECK(asset);
- CHECK(android::kInvalidCookie != cookie);
- const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
- CHECK(nullptr != dynamic_ref_table);
- android::ResXMLTree xml_tree{dynamic_ref_table};
- xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
- asset->getLength(),
- /*copy_data=*/true);
- android::ResXMLParser parser{xml_tree};
- parser.restart();
- if (CanCompileLayout(&parser)) {
- parser.restart();
- const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
- ResXmlVisitorAdapter adapter{&parser};
- switch (target) {
- case CompilationTarget::kDex: {
- methods.push_back(compiled_view.CreateMethod(
- layout_name,
- dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
- dex::TypeDescriptor::FromClassname("android.content.Context"),
- dex::TypeDescriptor::Int()}));
- DexViewBuilder builder(&methods.back());
- builder.Start();
- LayoutCompilerVisitor visitor{&builder};
- adapter.Accept(&visitor);
- builder.Finish();
- methods.back().Encode();
- break;
- }
- case CompilationTarget::kJavaLanguage: {
- JavaLangViewBuilder builder{package_name, layout_name, target_out};
- builder.Start();
- LayoutCompilerVisitor visitor{&builder};
- adapter.Accept(&visitor);
- builder.Finish();
- break;
- }
- }
- }
- });
- }
+ assets->GetAssetsProvider()->ForEachFile("res/", [&](android::StringPiece s, android::FileType) {
+ if (s == "layout") {
+ auto path = StringPrintf("res/%.*s/", (int)s.size(), s.data());
+ assets->GetAssetsProvider()
+ ->ForEachFile(path, [&](android::StringPiece layout_file, android::FileType) {
+ auto layout_path = StringPrintf("%s%.*s", path.c_str(),
+ (int)layout_file.size(), layout_file.data());
+ android::ApkAssetsCookie cookie = android::kInvalidCookie;
+ auto asset = resources.OpenNonAsset(layout_path,
+ android::Asset::ACCESS_RANDOM, &cookie);
+ CHECK(asset);
+ CHECK(android::kInvalidCookie != cookie);
+ const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+ CHECK(nullptr != dynamic_ref_table);
+ android::ResXMLTree xml_tree{dynamic_ref_table};
+ xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true), asset->getLength(),
+ /*copy_data=*/true);
+ android::ResXMLParser parser{xml_tree};
+ parser.restart();
+ if (CanCompileLayout(&parser)) {
+ parser.restart();
+ const std::string layout_name =
+ startop::util::FindLayoutNameFromFilename(layout_path);
+ ResXmlVisitorAdapter adapter{&parser};
+ switch (target) {
+ case CompilationTarget::kDex: {
+ methods.push_back(compiled_view.CreateMethod(
+ layout_name,
+ dex::Prototype{dex::TypeDescriptor::FromClassname(
+ "android.view.View"),
+ dex::TypeDescriptor::FromClassname(
+ "android.content.Context"),
+ dex::TypeDescriptor::Int()}));
+ DexViewBuilder builder(&methods.back());
+ builder.Start();
+ LayoutCompilerVisitor visitor{&builder};
+ adapter.Accept(&visitor);
+ builder.Finish();
+ methods.back().Encode();
+ break;
+ }
+ case CompilationTarget::kJavaLanguage: {
+ JavaLangViewBuilder builder{package_name, layout_name,
+ target_out};
+ builder.Start();
+ LayoutCompilerVisitor visitor{&builder};
+ adapter.Accept(&visitor);
+ builder.Finish();
+ break;
+ }
+ }
+ }
+ });
+ }
});
if (target == CompilationTarget::kDex) {
diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java
index 86b98f1..2435243 100644
--- a/telephony/java/android/telephony/Annotation.java
+++ b/telephony/java/android/telephony/Annotation.java
@@ -5,6 +5,7 @@
import android.net.NetworkCapabilities;
import android.telecom.Connection;
import android.telephony.data.ApnSetting;
+import android.telephony.ims.ImsCallProfile;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -494,7 +495,7 @@
PreciseCallState.PRECISE_CALL_STATE_HOLDING,
PreciseCallState.PRECISE_CALL_STATE_DIALING,
PreciseCallState.PRECISE_CALL_STATE_ALERTING,
- PreciseCallState. PRECISE_CALL_STATE_INCOMING,
+ PreciseCallState.PRECISE_CALL_STATE_INCOMING,
PreciseCallState.PRECISE_CALL_STATE_WAITING,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED,
PreciseCallState.PRECISE_CALL_STATE_DISCONNECTING})
@@ -727,6 +728,36 @@
})
public @interface ValidationStatus {}
+ /**
+ * IMS call Service types
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "SERVICE_TYPE_" }, value = {
+ ImsCallProfile.SERVICE_TYPE_NONE,
+ ImsCallProfile.SERVICE_TYPE_NORMAL,
+ ImsCallProfile.SERVICE_TYPE_EMERGENCY,
+ })
+ public @interface ImsCallServiceType {}
+
+ /**
+ * IMS call types
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "CALL_TYPE_" }, value = {
+ ImsCallProfile.CALL_TYPE_NONE,
+ ImsCallProfile.CALL_TYPE_VOICE_N_VIDEO,
+ ImsCallProfile.CALL_TYPE_VOICE,
+ ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE,
+ ImsCallProfile.CALL_TYPE_VT,
+ ImsCallProfile.CALL_TYPE_VT_TX,
+ ImsCallProfile.CALL_TYPE_VT_RX,
+ ImsCallProfile.CALL_TYPE_VT_NODIR,
+ ImsCallProfile.CALL_TYPE_VS,
+ ImsCallProfile.CALL_TYPE_VS_TX,
+ ImsCallProfile.CALL_TYPE_VS_RX,
+ })
+ public @interface ImsCallType {}
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "NET_CAPABILITY_ENTERPRISE_SUB_LEVEL" }, value = {
diff --git a/telephony/java/android/telephony/CallAttributes.java b/telephony/java/android/telephony/CallAttributes.java
index b7bef39..1dc64a9 100644
--- a/telephony/java/android/telephony/CallAttributes.java
+++ b/telephony/java/android/telephony/CallAttributes.java
@@ -29,8 +29,10 @@
* Contains information about a call's attributes as passed up from the HAL. If there are multiple
* ongoing calls, the CallAttributes will pertain to the call in the foreground.
* @hide
+ * @deprecated use {@link CallState} for call information for each call.
*/
@SystemApi
+@Deprecated
public final class CallAttributes implements Parcelable {
private PreciseCallState mPreciseCallState;
@NetworkType
diff --git a/telephony/java/android/telephony/CallState.aidl b/telephony/java/android/telephony/CallState.aidl
new file mode 100644
index 0000000..dd5af8e
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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 android.telephony;
+
+parcelable CallState;
+
diff --git a/telephony/java/android/telephony/CallState.java b/telephony/java/android/telephony/CallState.java
new file mode 100644
index 0000000..51ecfb0
--- /dev/null
+++ b/telephony/java/android/telephony/CallState.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.Annotation.ImsCallServiceType;
+import android.telephony.Annotation.ImsCallType;
+import android.telephony.Annotation.NetworkType;
+import android.telephony.Annotation.PreciseCallStates;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSession;
+
+import java.util.Objects;
+
+/**
+ * Contains information about various states for a call.
+ * @hide
+ */
+@SystemApi
+public final class CallState implements Parcelable {
+
+ /**
+ * Call classifications are just used for backward compatibility of deprecated API {@link
+ * TelephonyCallback#CallAttributesListener#onCallAttributesChanged}, Since these will be
+ * removed when the deprecated API is removed, they should not be opened.
+ */
+ /**
+ * Call classification is not valid. It should not be opened.
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_UNKNOWN = -1;
+
+ /**
+ * Call classification indicating foreground call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_RINGING = 0;
+
+ /**
+ * Call classification indicating background call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_FOREGROUND = 1;
+
+ /**
+ * Call classification indicating ringing call
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_BACKGROUND = 2;
+
+ /**
+ * Call classification Max value.
+ * @hide
+ */
+ public static final int CALL_CLASSIFICATION_MAX = CALL_CLASSIFICATION_BACKGROUND + 1;
+
+ @PreciseCallStates
+ private final int mPreciseCallState;
+
+ @NetworkType
+ private final int mNetworkType; // TelephonyManager.NETWORK_TYPE_* ints
+ private final CallQuality mCallQuality;
+
+ private final int mCallClassification;
+ /**
+ * IMS call session ID. {@link ImsCallSession#getCallId()}
+ */
+ @Nullable
+ private String mImsCallId;
+
+ /**
+ * IMS call service type of this call
+ */
+ @ImsCallServiceType
+ private int mImsCallServiceType;
+
+ /**
+ * IMS call type of this call.
+ */
+ @ImsCallType
+ private int mImsCallType;
+
+ /**
+ * Constructor of CallAttributes
+ *
+ * @param callState call state defined in {@link PreciseCallState}
+ * @param networkType network type for this call attributes
+ * @param callQuality call quality for this call attributes, only CallState in
+ * {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call
+ * quality.
+ * @param callClassification call classification
+ * @param imsCallId IMS call session ID for this call attributes
+ * @param imsCallServiceType IMS call service type for this call attributes
+ * @param imsCallType IMS call type for this call attributes
+ */
+ private CallState(@PreciseCallStates int callState, @NetworkType int networkType,
+ @NonNull CallQuality callQuality, int callClassification, @Nullable String imsCallId,
+ @ImsCallServiceType int imsCallServiceType, @ImsCallType int imsCallType) {
+ this.mPreciseCallState = callState;
+ this.mNetworkType = networkType;
+ this.mCallQuality = callQuality;
+ this.mCallClassification = callClassification;
+ this.mImsCallId = imsCallId;
+ this.mImsCallServiceType = imsCallServiceType;
+ this.mImsCallType = imsCallType;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "mPreciseCallState=" + mPreciseCallState + " mNetworkType=" + mNetworkType
+ + " mCallQuality=" + mCallQuality + " mCallClassification" + mCallClassification
+ + " mImsCallId=" + mImsCallId + " mImsCallServiceType=" + mImsCallServiceType
+ + " mImsCallType=" + mImsCallType;
+ }
+
+ private CallState(Parcel in) {
+ this.mPreciseCallState = in.readInt();
+ this.mNetworkType = in.readInt();
+ this.mCallQuality = in.readParcelable(
+ CallQuality.class.getClassLoader(), CallQuality.class);
+ this.mCallClassification = in.readInt();
+ this.mImsCallId = in.readString();
+ this.mImsCallServiceType = in.readInt();
+ this.mImsCallType = in.readInt();
+ }
+
+ // getters
+ /**
+ * Returns the precise call state of the call.
+ */
+ @PreciseCallStates
+ public int getCallState() {
+ return mPreciseCallState;
+ }
+
+ /**
+ * Returns the {@link TelephonyManager#NetworkType} of the call.
+ *
+ * @see TelephonyManager#NETWORK_TYPE_UNKNOWN
+ * @see TelephonyManager#NETWORK_TYPE_GPRS
+ * @see TelephonyManager#NETWORK_TYPE_EDGE
+ * @see TelephonyManager#NETWORK_TYPE_UMTS
+ * @see TelephonyManager#NETWORK_TYPE_CDMA
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_0
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_A
+ * @see TelephonyManager#NETWORK_TYPE_1xRTT
+ * @see TelephonyManager#NETWORK_TYPE_HSDPA
+ * @see TelephonyManager#NETWORK_TYPE_HSUPA
+ * @see TelephonyManager#NETWORK_TYPE_HSPA
+ * @see TelephonyManager#NETWORK_TYPE_IDEN
+ * @see TelephonyManager#NETWORK_TYPE_EVDO_B
+ * @see TelephonyManager#NETWORK_TYPE_LTE
+ * @see TelephonyManager#NETWORK_TYPE_EHRPD
+ * @see TelephonyManager#NETWORK_TYPE_HSPAP
+ * @see TelephonyManager#NETWORK_TYPE_GSM
+ * @see TelephonyManager#NETWORK_TYPE_TD_SCDMA
+ * @see TelephonyManager#NETWORK_TYPE_IWLAN
+ * @see TelephonyManager#NETWORK_TYPE_LTE_CA
+ * @see TelephonyManager#NETWORK_TYPE_NR
+ */
+ @NetworkType
+ public int getNetworkType() {
+ return mNetworkType;
+ }
+
+ /**
+ * Returns the {#link CallQuality} of the call.
+ * @return call quality for this call attributes, only CallState in {@link
+ * PreciseCallState#PRECISE_CALL_STATE_ACTIVE} will have valid call quality. It will be
+ * null for the call which is not in {@link PreciseCallState#PRECISE_CALL_STATE_ACTIVE}.
+ */
+ @Nullable
+ public CallQuality getCallQuality() {
+ return mCallQuality;
+ }
+
+ /**
+ * Returns the call classification.
+ * @hide
+ */
+ public int getCallClassification() {
+ return mCallClassification;
+ }
+
+ /**
+ * Returns the IMS call session ID.
+ */
+ @Nullable
+ public String getImsCallSessionId() {
+ return mImsCallId;
+ }
+
+ /**
+ * Returns the IMS call service type.
+ */
+ @ImsCallServiceType
+ public int getImsCallServiceType() {
+ return mImsCallServiceType;
+ }
+
+ /**
+ * Returns the IMS call type.
+ */
+ @ImsCallType
+ public int getImsCallType() {
+ return mImsCallType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPreciseCallState, mNetworkType, mCallQuality, mCallClassification,
+ mImsCallId, mImsCallServiceType, mImsCallType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o == null || !(o instanceof CallState) || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ if (this == o) {
+ return true;
+ }
+
+ CallState s = (CallState) o;
+
+ return (mPreciseCallState == s.mPreciseCallState
+ && mNetworkType == s.mNetworkType
+ && Objects.equals(mCallQuality, s.mCallQuality)
+ && mCallClassification == s.mCallClassification
+ && Objects.equals(mImsCallId, s.mImsCallId)
+ && mImsCallType == s.mImsCallType
+ && mImsCallServiceType == s.mImsCallServiceType);
+ }
+
+ /**
+ * {@link Parcelable#describeContents}
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * {@link Parcelable#writeToParcel}
+ */
+ public void writeToParcel(@Nullable Parcel dest, int flags) {
+ dest.writeInt(mPreciseCallState);
+ dest.writeInt(mNetworkType);
+ dest.writeParcelable(mCallQuality, flags);
+ dest.writeInt(mCallClassification);
+ dest.writeString(mImsCallId);
+ dest.writeInt(mImsCallServiceType);
+ dest.writeInt(mImsCallType);
+ }
+
+ public static final @NonNull Creator<CallState> CREATOR = new Creator() {
+ public CallState createFromParcel(Parcel in) {
+ return new CallState(in);
+ }
+
+ public CallState[] newArray(int size) {
+ return new CallState[size];
+ }
+ };
+
+ /**
+ * Builder of {@link CallState}
+ *
+ * <p>The example below shows how you might create a new {@code CallState}:
+ *
+ * <pre><code>
+ *
+ * CallState = new CallState.Builder()
+ * .setCallState(3)
+ * .setNetworkType({@link TelephonyManager#NETWORK_TYPE_LTE})
+ * .setCallQuality({@link CallQuality})
+ * .setImsCallSessionId({@link String})
+ * .setImsCallServiceType({@link ImsCallProfile#SERVICE_TYPE_NORMAL})
+ * .setImsCallType({@link ImsCallProfile#CALL_TYPE_VOICE})
+ * .build();
+ * </code></pre>
+ */
+ public static final class Builder {
+ private @PreciseCallStates int mPreciseCallState;
+ private @NetworkType int mNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private CallQuality mCallQuality = null;
+ private int mCallClassification = CALL_CLASSIFICATION_UNKNOWN;
+ private String mImsCallId;
+ private @ImsCallServiceType int mImsCallServiceType = ImsCallProfile.SERVICE_TYPE_NONE;
+ private @ImsCallType int mImsCallType = ImsCallProfile.CALL_TYPE_NONE;
+
+
+ /**
+ * Default constructor for the Builder.
+ */
+ public Builder(@PreciseCallStates int preciseCallState) {
+ mPreciseCallState = preciseCallState;
+ }
+
+ /**
+ * Set network type of this call.
+ *
+ * @param networkType the transport type.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setNetworkType(@NetworkType int networkType) {
+ this.mNetworkType = networkType;
+ return this;
+ }
+
+ /**
+ * Set the call quality {@link CallQuality} of this call.
+ *
+ * @param callQuality call quality of active call.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setCallQuality(@Nullable CallQuality callQuality) {
+ this.mCallQuality = callQuality;
+ return this;
+ }
+
+ /**
+ * Set call classification for this call.
+ *
+ * @param classification call classification type defined in this class.
+ * @return The same instance of the builder.
+ * @hide
+ */
+ @NonNull
+ public CallState.Builder setCallClassification(int classification) {
+ this.mCallClassification = classification;
+ return this;
+ }
+
+ /**
+ * Set IMS call session ID of this call.
+ *
+ * @param imsCallId IMS call session ID.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallSessionId(@Nullable String imsCallId) {
+ this.mImsCallId = imsCallId;
+ return this;
+ }
+
+ /**
+ * Set IMS call service type of this call.
+ *
+ * @param serviceType IMS call service type defined in {@link ImsCallProfile}.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallServiceType(@ImsCallServiceType int serviceType) {
+ this.mImsCallServiceType = serviceType;
+ return this;
+ }
+
+ /**
+ * Set IMS call type of this call.
+ *
+ * @param callType IMS call type defined in {@link ImsCallProfile}.
+ * @return The same instance of the builder.
+ */
+ @NonNull
+ public CallState.Builder setImsCallType(@ImsCallType int callType) {
+ this.mImsCallType = callType;
+ return this;
+ }
+
+ /**
+ * Build the {@link CallState}
+ *
+ * @return the {@link CallState} object
+ */
+ @NonNull
+ public CallState build() {
+ return new CallState(
+ mPreciseCallState,
+ mNetworkType,
+ mCallQuality,
+ mCallClassification,
+ mImsCallId,
+ mImsCallServiceType,
+ mImsCallType);
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index e6d7df3..1ea7fdc 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -78,8 +78,9 @@
public static final int SERVICE_TYPE_EMERGENCY = 2;
/**
- * Call types
+ * Call type none
*/
+ public static final int CALL_TYPE_NONE = 0;
/**
* IMSPhone to support IR.92 & IR.94 (voice + video upgrade/downgrade)
*/
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 0c14dba..a1257e3 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -546,6 +546,8 @@
int RIL_REQUEST_UPDATE_IMS_CALL_STATUS = 240;
int RIL_REQUEST_SET_N1_MODE_ENABLED = 241;
int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
+ int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
+ int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
/* Responses begin */
int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -620,4 +622,5 @@
int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
int RIL_UNSOL_NOTIFY_ANBR = 1109;
+ int RIL_UNSOL_ON_NETWORK_INITIATED_LOCATION_RESULT = 1110;
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 8a1e1fa..3f6a75d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -172,4 +172,17 @@
open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
testSpec.assertWm { this.visibleWindowsShownMoreThanOneConsecutiveEntry() }
}
+
+ open fun cujCompleted() {
+ entireScreenCovered()
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
+ statusBarLayerIsVisibleAtStartAndEnd()
+ statusBarLayerPositionAtStartAndEnd()
+ statusBarWindowIsAlwaysVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
new file mode 100644
index 0000000..945de33
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "ironwood-postsubmit": [
+ {
+ "name": "FlickerTests",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
index b9c875a..ef42766 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToAppTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.BaseTest
@@ -101,6 +102,16 @@
testSpec.assertWm { this.isAppWindowOnTop(testApp) }
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ navBarLayerPositionAtStartAndEnd()
+ imeLayerBecomesInvisible()
+ imeAppLayerIsAlwaysVisible()
+ imeAppWindowIsAlwaysVisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
index 1dc3ca5..c92fce3 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeWindowToHomeTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.ime
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -100,6 +101,17 @@
testSpec.assertLayers { this.isVisible(testApp).then().isInvisible(testApp) }
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ navBarLayerPositionAtStartAndEnd()
+ imeLayerBecomesInvisible()
+ imeAppWindowBecomesInvisible()
+ imeWindowBecomesInvisible()
+ imeLayerBecomesInvisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
index a6bd791..7d7953b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowAndCloseTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.ime
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -79,6 +80,14 @@
super.visibleLayersShownMoreThanOneConsecutiveEntry()
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ imeLayerBecomesInvisible()
+ imeWindowBecomesInvisible()
+ }
+
companion object {
@Parameterized.Parameters(name = "{0}")
@JvmStatic
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
index b43efea..9919d87 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowTest.kt
@@ -16,6 +16,7 @@
package com.android.server.wm.flicker.ime
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.view.Surface
import android.view.WindowManagerPolicyConstants
@@ -50,6 +51,15 @@
}
}
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ imeWindowBecomesVisible()
+ appWindowAlwaysVisibleOnTop()
+ layerAlwaysVisible()
+ }
+
@Presubmit @Test fun imeWindowBecomesVisible() = testSpec.imeWindowBecomesVisible()
@Presubmit
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
index 1973ec0..ad14d0d 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/ChangeAppRotationTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
@@ -125,6 +126,14 @@
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ super.cujCompleted()
+ focusChanges()
+ rotationLayerAppearsAndVanishes()
+ }
+
companion object {
/**
* Creates the test configurations.
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index 4faeb24..8e3fd40 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -73,4 +73,10 @@
}
}
}
+
+ override fun cujCompleted() {
+ super.cujCompleted()
+ appLayerRotates_StartingPos()
+ appLayerRotates_EndingPos()
+ }
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
index a08db29..d0d4122 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/rotation/SeamlessAppRotationTest.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.IwTest
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.WindowManager
@@ -204,6 +205,31 @@
@Test
override fun navBarLayerPositionAtStartAndEnd() = super.navBarLayerPositionAtStartAndEnd()
+ @Test
+ @IwTest(focusArea = "ime")
+ override fun cujCompleted() {
+ if (!testSpec.isTablet) {
+ // not yet tablet compatible
+ appLayerRotates()
+ appLayerAlwaysVisible()
+ }
+
+ appWindowFullScreen()
+ appWindowSeamlessRotation()
+ focusDoesNotChange()
+ statusBarLayerIsAlwaysInvisible()
+ statusBarWindowIsAlwaysInvisible()
+ appLayerRotates_StartingPos()
+ appLayerRotates_EndingPos()
+ entireScreenCovered()
+ navBarLayerIsVisibleAtStartAndEnd()
+ navBarWindowIsAlwaysVisible()
+ taskBarLayerIsVisibleAtStartAndEnd()
+ taskBarWindowIsAlwaysVisible()
+ visibleLayersShownMoreThanOneConsecutiveEntry()
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ }
+
companion object {
private val FlickerTestParameter.starveUiThread
get() =
diff --git a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
index d133f6f..e2099e6 100644
--- a/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
+++ b/tests/UsbManagerTests/lib/src/com/android/server/usblib/UsbManagerTestLib.java
@@ -24,12 +24,15 @@
import android.content.Context;
import android.hardware.usb.UsbManager;
+import android.os.Binder;
import android.os.RemoteException;
import android.util.Log;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
* Unit tests lib for {@link android.hardware.usb.UsbManager}.
*/
@@ -42,6 +45,11 @@
private UsbManager mUsbManagerMock;
@Mock private android.hardware.usb.IUsbManager mMockUsbService;
+ /**
+ * Counter for tracking UsbOperation operations.
+ */
+ private static final AtomicInteger sUsbOperationCount = new AtomicInteger();
+
public UsbManagerTestLib(Context context) {
MockitoAnnotations.initMocks(this);
mContext = context;
@@ -82,10 +90,11 @@
}
private void testSetCurrentFunctionsMock_Matched(long functions) {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
try {
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions));
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
} catch (RemoteException remEx) {
Log.w(TAG, "RemoteException");
}
@@ -106,9 +115,10 @@
}
public void testSetCurrentFunctionsEx(long functions) throws Exception {
+ int operationId = sUsbOperationCount.incrementAndGet() + Binder.getCallingUid();
setCurrentFunctions(functions);
- verify(mMockUsbService).setCurrentFunctions(eq(functions));
+ verify(mMockUsbService).setCurrentFunctions(eq(functions), operationId);
}
public void testGetCurrentFunctions_shouldMatched() {
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 86bcb72..4103ca7 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -98,7 +98,7 @@
}
@Override
- protected void setEnabledFunctions(long functions, boolean force) {
+ protected void setEnabledFunctions(long functions, boolean force, int operationId) {
mCurrentFunctions = functions;
}
@@ -134,6 +134,20 @@
protected void sendStickyBroadcast(Intent intent) {
mBroadcastedIntent = intent;
}
+
+ @Override
+ public void handlerInitDone(int operationId) {
+ }
+
+ @Override
+ public void setCurrentUsbFunctionsCb(long functions,
+ int status, int mRequest, long mFunctions, boolean mChargingFunctions){
+ }
+
+ @Override
+ public void getUsbSpeedCb(int speed){
+ }
+
}
@Before
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 9b9cde2..6b1fd9f 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -72,7 +72,7 @@
}
}
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path,
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(StringPiece path,
android::IDiagnostics* diag) {
android::Source source(path);
std::string error;
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index a4aff3f..4cd7eae 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -45,7 +45,7 @@
virtual ~LoadedApk() = default;
// Loads both binary and proto APKs from disk.
- static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+ static std::unique_ptr<LoadedApk> LoadApkFromPath(android::StringPiece path,
android::IDiagnostics* diag);
// Loads a proto APK from the given file collection.
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
index 0b49052..0b08c32 100644
--- a/tools/aapt2/NameMangler.h
+++ b/tools/aapt2/NameMangler.h
@@ -36,7 +36,7 @@
* We must know which references to mangle, and which to keep (android vs.
* com.android.support).
*/
- std::set<std::string> packages_to_mangle;
+ std::set<std::string, std::less<>> packages_to_mangle;
};
class NameMangler {
@@ -54,7 +54,7 @@
mangled_entry_name);
}
- bool ShouldMangle(const std::string& package) const {
+ bool ShouldMangle(std::string_view package) const {
if (package.empty() || policy_.target_package_name == package) {
return false;
}
@@ -68,8 +68,8 @@
* The mangled name should contain symbols that are illegal to define in XML,
* so that there will never be name mangling collisions.
*/
- static std::string MangleEntry(const std::string& package, const std::string& name) {
- return package + "$" + name;
+ static std::string MangleEntry(std::string_view package, std::string_view name) {
+ return (std::string(package) += '$') += name;
}
/**
diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp
index df8c3b9..cfcb2bb 100644
--- a/tools/aapt2/Resource.cpp
+++ b/tools/aapt2/Resource.cpp
@@ -138,11 +138,11 @@
return {to_string(t), t};
}
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s) {
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s) {
auto dot = std::find(s.begin(), s.end(), '.');
const ResourceType* parsedType;
if (dot != s.end() && dot != std::prev(s.end())) {
- parsedType = ParseResourceType(s.substr(s.begin(), dot));
+ parsedType = ParseResourceType(android::StringPiece(s.begin(), dot - s.begin()));
} else {
parsedType = ParseResourceType(s);
}
@@ -152,7 +152,7 @@
return ResourceNamedTypeRef(s, *parsedType);
}
-const ResourceType* ParseResourceType(const StringPiece& str) {
+const ResourceType* ParseResourceType(StringPiece str) {
auto iter = sResourceTypeMap.find(str);
if (iter == std::end(sResourceTypeMap)) {
return nullptr;
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 9cfaf47..7ba3277 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -74,7 +74,7 @@
/**
* Returns a pointer to a valid ResourceType, or nullptr if the string was invalid.
*/
-const ResourceType* ParseResourceType(const android::StringPiece& str);
+const ResourceType* ParseResourceType(android::StringPiece str);
/**
* Pair of type name as in ResourceTable and actual resource type.
@@ -87,7 +87,7 @@
ResourceType type = ResourceType::kRaw;
ResourceNamedType() = default;
- ResourceNamedType(const android::StringPiece& n, ResourceType t);
+ ResourceNamedType(android::StringPiece n, ResourceType t);
int compare(const ResourceNamedType& other) const;
@@ -108,19 +108,19 @@
ResourceNamedTypeRef(const ResourceNamedTypeRef&) = default;
ResourceNamedTypeRef(ResourceNamedTypeRef&&) = default;
ResourceNamedTypeRef(const ResourceNamedType& rhs); // NOLINT(google-explicit-constructor)
- ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t);
+ ResourceNamedTypeRef(android::StringPiece n, ResourceType t);
ResourceNamedTypeRef& operator=(const ResourceNamedTypeRef& rhs) = default;
ResourceNamedTypeRef& operator=(ResourceNamedTypeRef&& rhs) = default;
ResourceNamedTypeRef& operator=(const ResourceNamedType& rhs);
ResourceNamedType ToResourceNamedType() const;
- std::string to_string() const;
+ std::string_view to_string() const;
};
ResourceNamedTypeRef ResourceNamedTypeWithDefaultName(ResourceType t);
-std::optional<ResourceNamedTypeRef> ParseResourceNamedType(const android::StringPiece& s);
+std::optional<ResourceNamedTypeRef> ParseResourceNamedType(android::StringPiece s);
/**
* A resource's name. This can uniquely identify
@@ -132,9 +132,8 @@
std::string entry;
ResourceName() = default;
- ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
- const android::StringPiece& e);
- ResourceName(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+ ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+ ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e);
int compare(const ResourceName& other) const;
@@ -157,9 +156,8 @@
ResourceNameRef(const ResourceNameRef&) = default;
ResourceNameRef(ResourceNameRef&&) = default;
ResourceNameRef(const ResourceName& rhs); // NOLINT(google-explicit-constructor)
- ResourceNameRef(const android::StringPiece& p, const ResourceNamedTypeRef& t,
- const android::StringPiece& e);
- ResourceNameRef(const android::StringPiece& p, ResourceType t, const android::StringPiece& e);
+ ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t, android::StringPiece e);
+ ResourceNameRef(android::StringPiece p, ResourceType t, android::StringPiece e);
ResourceNameRef& operator=(const ResourceNameRef& rhs) = default;
ResourceNameRef& operator=(ResourceNameRef&& rhs) = default;
ResourceNameRef& operator=(const ResourceName& rhs);
@@ -346,8 +344,8 @@
//
// ResourceNamedType implementation.
//
-inline ResourceNamedType::ResourceNamedType(const android::StringPiece& n, ResourceType t)
- : name(n.to_string()), type(t) {
+inline ResourceNamedType::ResourceNamedType(android::StringPiece n, ResourceType t)
+ : name(n), type(t) {
}
inline int ResourceNamedType::compare(const ResourceNamedType& other) const {
@@ -380,7 +378,7 @@
//
// ResourceNamedTypeRef implementation.
//
-inline ResourceNamedTypeRef::ResourceNamedTypeRef(const android::StringPiece& n, ResourceType t)
+inline ResourceNamedTypeRef::ResourceNamedTypeRef(android::StringPiece n, ResourceType t)
: name(n), type(t) {
}
@@ -398,8 +396,8 @@
return ResourceNamedType(name, type);
}
-inline std::string ResourceNamedTypeRef::to_string() const {
- return name.to_string();
+inline std::string_view ResourceNamedTypeRef::to_string() const {
+ return name;
}
inline bool operator<(const ResourceNamedTypeRef& lhs, const ResourceNamedTypeRef& rhs) {
@@ -422,13 +420,12 @@
// ResourceName implementation.
//
-inline ResourceName::ResourceName(const android::StringPiece& p, const ResourceNamedTypeRef& t,
- const android::StringPiece& e)
- : package(p.to_string()), type(t.ToResourceNamedType()), entry(e.to_string()) {
+inline ResourceName::ResourceName(android::StringPiece p, const ResourceNamedTypeRef& t,
+ android::StringPiece e)
+ : package(p), type(t.ToResourceNamedType()), entry(e) {
}
-inline ResourceName::ResourceName(const android::StringPiece& p, ResourceType t,
- const android::StringPiece& e)
+inline ResourceName::ResourceName(android::StringPiece p, ResourceType t, android::StringPiece e)
: ResourceName(p, ResourceNamedTypeWithDefaultName(t), e) {
}
@@ -471,14 +468,13 @@
inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs)
: package(rhs.package), type(rhs.type), entry(rhs.entry) {}
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p,
- const ResourceNamedTypeRef& t,
- const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, const ResourceNamedTypeRef& t,
+ android::StringPiece e)
: package(p), type(t), entry(e) {
}
-inline ResourceNameRef::ResourceNameRef(const android::StringPiece& p, ResourceType t,
- const android::StringPiece& e)
+inline ResourceNameRef::ResourceNameRef(android::StringPiece p, ResourceType t,
+ android::StringPiece e)
: ResourceNameRef(p, ResourceNamedTypeWithDefaultName(t), e) {
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 19fd306..fa9a98f 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -50,11 +50,11 @@
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
// Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
-static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
+static bool ShouldIgnoreElement(StringPiece ns, StringPiece name) {
return ns.empty() && (name == "skip" || name == "eat-comment");
}
-static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
+static uint32_t ParseFormatTypeNoEnumsOrFlags(StringPiece piece) {
if (piece == "reference") {
return android::ResTable_map::TYPE_REFERENCE;
} else if (piece == "string") {
@@ -75,7 +75,7 @@
return 0;
}
-static uint32_t ParseFormatType(const StringPiece& piece) {
+static uint32_t ParseFormatType(StringPiece piece) {
if (piece == "enum") {
return android::ResTable_map::TYPE_ENUM;
} else if (piece == "flags") {
@@ -84,9 +84,9 @@
return ParseFormatTypeNoEnumsOrFlags(piece);
}
-static uint32_t ParseFormatAttribute(const StringPiece& str) {
+static uint32_t ParseFormatAttribute(StringPiece str) {
uint32_t mask = 0;
- for (const StringPiece& part : util::Tokenize(str, '|')) {
+ for (StringPiece part : util::Tokenize(str, '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
uint32_t type = ParseFormatType(trimmed_part);
if (type == 0) {
@@ -122,7 +122,7 @@
StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
if (trimmed_comment.size() != res->comment.size()) {
// Only if there was a change do we re-assign.
- res->comment = trimmed_comment.to_string();
+ res->comment = std::string(trimmed_comment);
}
NewResourceBuilder res_builder(res->name);
@@ -362,7 +362,7 @@
// Trim leading whitespace.
StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
if (trimmed.size() != first_segment->data.size()) {
- first_segment->data = trimmed.to_string();
+ first_segment->data = std::string(trimmed);
}
}
@@ -370,7 +370,7 @@
// Trim trailing whitespace.
StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
if (trimmed.size() != last_segment->data.size()) {
- last_segment->data = trimmed.to_string();
+ last_segment->data = std::string(trimmed);
}
}
}
@@ -466,7 +466,7 @@
// Extract the product name if it exists.
if (std::optional<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
- parsed_resource.product = maybe_product.value().to_string();
+ parsed_resource.product = std::string(maybe_product.value());
}
// Parse the resource regardless of product.
@@ -559,7 +559,7 @@
// Items have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = maybe_type.value().to_string();
+ resource_type = std::string(maybe_type.value());
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<item> must have a 'type' attribute");
@@ -582,7 +582,7 @@
// Bags have their type encoded in the type attribute.
if (std::optional<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
- resource_type = maybe_type.value().to_string();
+ resource_type = std::string(maybe_type.value());
} else {
diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
<< "<bag> must have a 'type' attribute");
@@ -603,7 +603,7 @@
out_resource->name.type =
ResourceNamedTypeWithDefaultName(ResourceType::kId).ToResourceNamedType();
- out_resource->name.entry = maybe_name.value().to_string();
+ out_resource->name.entry = std::string(maybe_name.value());
// Ids either represent a unique resource id or reference another resource id
auto item = ParseItem(parser, out_resource, resource_format);
@@ -640,7 +640,7 @@
out_resource->name.type =
ResourceNamedTypeWithDefaultName(ResourceType::kMacro).ToResourceNamedType();
- out_resource->name.entry = maybe_name.value().to_string();
+ out_resource->name.entry = std::string(maybe_name.value());
return ParseMacro(parser, out_resource);
}
@@ -657,7 +657,7 @@
out_resource->name.type =
ResourceNamedTypeWithDefaultName(item_iter->second.type).ToResourceNamedType();
- out_resource->name.entry = maybe_name.value().to_string();
+ out_resource->name.entry = std::string(maybe_name.value());
// Only use the implied format of the type when there is no explicit format.
if (resource_format == 0u) {
@@ -684,7 +684,7 @@
return false;
}
- out_resource->name.entry = maybe_name.value().to_string();
+ out_resource->name.entry = std::string(maybe_name.value());
}
// Call the associated parse method. The type will be filled in by the
@@ -708,7 +708,7 @@
}
out_resource->name.type = parsed_type->ToResourceNamedType();
- out_resource->name.entry = maybe_name.value().to_string();
+ out_resource->name.entry = std::string(maybe_name.value());
out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
if (!out_resource->value) {
diag_->Error(android::DiagMessage(out_resource->source)
@@ -1005,7 +1005,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).to_string();
+ comment = std::string(util::TrimWhitespace(parser->comment()));
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
@@ -1045,7 +1045,7 @@
}
ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
- .name = ResourceName{{}, parsed_type, maybe_name.value().to_string()},
+ .name = ResourceName{{}, parsed_type, std::string(maybe_name.value())},
.source = item_source,
.comment = std::move(comment),
});
@@ -1231,7 +1231,7 @@
ParsedResource child_resource{};
child_resource.name.type = type->ToResourceNamedType();
- child_resource.name.entry = item_name.value().to_string();
+ child_resource.name.entry = std::string(item_name.value());
child_resource.overlayable_item = overlayable_item;
out_resource->child_resources.push_back(std::move(child_resource));
@@ -1246,7 +1246,7 @@
xml::FindNonEmptyAttribute(parser, "type")) {
// Parse the polices separated by vertical bar characters to allow for specifying multiple
// policies. Items within the policy tag will have the specified policy.
- for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
+ for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
const auto policy = std::find_if(kPolicyStringToFlag.begin(),
kPolicyStringToFlag.end(),
@@ -1377,7 +1377,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).to_string();
+ comment = std::string(util::TrimWhitespace(parser->comment()));
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
@@ -1457,7 +1457,7 @@
}
std::optional<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(xml::XmlPullParser* parser,
- const StringPiece& tag) {
+ StringPiece tag) {
const android::Source source = source_.WithLine(parser->line_number());
std::optional<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
@@ -1764,7 +1764,7 @@
const size_t depth = parser->depth();
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
if (parser->event() == xml::XmlPullParser::Event::kComment) {
- comment = util::TrimWhitespace(parser->comment()).to_string();
+ comment = std::string(util::TrimWhitespace(parser->comment()));
continue;
} else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
// Ignore text.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 396ce97..012a056 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -122,7 +122,7 @@
bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
std::optional<Attribute::Symbol> ParseEnumOrFlagItem(xml::XmlPullParser* parser,
- const android::StringPiece& tag);
+ android::StringPiece tag);
bool ParseStyle(ResourceType type, xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseStyleItem(xml::XmlPullParser* parser, Style* style);
bool ParseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* out_resource);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index fe7eb96..b59b165 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -65,11 +65,11 @@
context_ = test::ContextBuilder().Build();
}
- ::testing::AssertionResult TestParse(const StringPiece& str) {
+ ::testing::AssertionResult TestParse(StringPiece str) {
return TestParse(str, ConfigDescription{});
}
- ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) {
+ ::testing::AssertionResult TestParse(StringPiece str, const ConfigDescription& config) {
ResourceParserOptions parserOptions;
ResourceParser parser(context_->GetDiagnostics(), &table_, android::Source{"test"}, config,
parserOptions);
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index cb48114..a3b0b45 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -49,21 +49,21 @@
}
template <typename T>
-bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, const StringPiece& rhs) {
+bool less_than_struct_with_name(const std::unique_ptr<T>& lhs, StringPiece rhs) {
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
}
template <typename T>
-bool greater_than_struct_with_name(const StringPiece& lhs, const std::unique_ptr<T>& rhs) {
+bool greater_than_struct_with_name(StringPiece lhs, const std::unique_ptr<T>& rhs) {
return rhs->name.compare(0, rhs->name.size(), lhs.data(), lhs.size()) > 0;
}
template <typename T>
struct NameEqualRange {
- bool operator()(const std::unique_ptr<T>& lhs, const StringPiece& rhs) const {
+ bool operator()(const std::unique_ptr<T>& lhs, StringPiece rhs) const {
return less_than_struct_with_name<T>(lhs, rhs);
}
- bool operator()(const StringPiece& lhs, const std::unique_ptr<T>& rhs) const {
+ bool operator()(StringPiece lhs, const std::unique_ptr<T>& rhs) const {
return greater_than_struct_with_name<T>(lhs, rhs);
}
};
@@ -78,7 +78,7 @@
}
template <typename T, typename Func, typename Elements>
-T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Func action) {
+T* FindElementsRunAction(android::StringPiece name, Elements& entries, Func action) {
const auto iter =
std::lower_bound(entries.begin(), entries.end(), name, less_than_struct_with_name<T>);
const bool found = iter != entries.end() && name == (*iter)->name;
@@ -87,7 +87,7 @@
struct ConfigKey {
const ConfigDescription* config;
- const StringPiece& product;
+ StringPiece product;
};
template <typename T>
@@ -104,12 +104,12 @@
ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
}
-ResourceTablePackage* ResourceTable::FindPackage(const android::StringPiece& name) const {
+ResourceTablePackage* ResourceTable::FindPackage(android::StringPiece name) const {
return FindElementsRunAction<ResourceTablePackage>(
name, packages, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
}
-ResourceTablePackage* ResourceTable::FindOrCreatePackage(const android::StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindOrCreatePackage(android::StringPiece name) {
return FindElementsRunAction<ResourceTablePackage>(name, packages, [&](bool found, auto& iter) {
return found ? iter->get() : packages.emplace(iter, new ResourceTablePackage(name))->get();
});
@@ -139,18 +139,18 @@
});
}
-ResourceEntry* ResourceTableType::CreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::CreateEntry(android::StringPiece name) {
return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
return entries.emplace(iter, new ResourceEntry(name))->get();
});
}
-ResourceEntry* ResourceTableType::FindEntry(const android::StringPiece& name) const {
+ResourceEntry* ResourceTableType::FindEntry(android::StringPiece name) const {
return FindElementsRunAction<ResourceEntry>(
name, entries, [&](bool found, auto& iter) { return found ? iter->get() : nullptr; });
}
-ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece& name) {
+ResourceEntry* ResourceTableType::FindOrCreateEntry(android::StringPiece name) {
return FindElementsRunAction<ResourceEntry>(name, entries, [&](bool found, auto& iter) {
return found ? iter->get() : entries.emplace(iter, new ResourceEntry(name))->get();
});
@@ -183,7 +183,7 @@
}
ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
- const StringPiece& product) {
+ StringPiece product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index f49ce81..bb286a8 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -71,12 +71,11 @@
struct Overlayable {
Overlayable() = default;
- Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
- : name(name.to_string()), actor(actor.to_string()) {}
- Overlayable(const android::StringPiece& name, const android::StringPiece& actor,
- const android::Source& source)
- : name(name.to_string()), actor(actor.to_string()), source(source) {
- }
+ Overlayable(android::StringPiece name, android::StringPiece actor) : name(name), actor(actor) {
+ }
+ Overlayable(android::StringPiece name, android::StringPiece actor, const android::Source& source)
+ : name(name), actor(actor), source(source) {
+ }
static const char* kActorScheme;
std::string name;
@@ -105,8 +104,9 @@
// The actual Value.
std::unique_ptr<Value> value;
- ResourceConfigValue(const android::ConfigDescription& config, const android::StringPiece& product)
- : config(config), product(product.to_string()) {}
+ ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
+ : config(config), product(product) {
+ }
private:
DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
@@ -136,7 +136,8 @@
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
- explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
+ explicit ResourceEntry(android::StringPiece name) : name(name) {
+ }
ResourceConfigValue* FindValue(const android::ConfigDescription& config,
android::StringPiece product = {});
@@ -144,7 +145,7 @@
android::StringPiece product = {}) const;
ResourceConfigValue* FindOrCreateValue(const android::ConfigDescription& config,
- const android::StringPiece& product);
+ android::StringPiece product);
std::vector<ResourceConfigValue*> FindAllValues(const android::ConfigDescription& config);
template <typename Func>
@@ -180,9 +181,9 @@
: named_type(type.ToResourceNamedType()) {
}
- ResourceEntry* CreateEntry(const android::StringPiece& name);
- ResourceEntry* FindEntry(const android::StringPiece& name) const;
- ResourceEntry* FindOrCreateEntry(const android::StringPiece& name);
+ ResourceEntry* CreateEntry(android::StringPiece name);
+ ResourceEntry* FindEntry(android::StringPiece name) const;
+ ResourceEntry* FindOrCreateEntry(android::StringPiece name);
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
@@ -194,7 +195,7 @@
std::vector<std::unique_ptr<ResourceTableType>> types;
- explicit ResourceTablePackage(const android::StringPiece& name) : name(name.to_string()) {
+ explicit ResourceTablePackage(android::StringPiece name) : name(name) {
}
ResourceTablePackage() = default;
@@ -319,8 +320,8 @@
// Returns the package struct with the given name, or nullptr if such a package does not
// exist. The empty string is a valid package and typically is used to represent the
// 'current' package before it is known to the ResourceTable.
- ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
- ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
+ ResourceTablePackage* FindPackage(android::StringPiece name) const;
+ ResourceTablePackage* FindOrCreatePackage(android::StringPiece name);
std::unique_ptr<ResourceTable> Clone() const;
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 0cf8473..54b98d1 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -187,7 +187,7 @@
static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
const ResourceNameRef& name,
Visibility::Level level,
- const StringPiece& comment) {
+ StringPiece comment) {
std::optional<ResourceTable::SearchResult> result = table.FindResource(name);
if (!result) {
return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 41c7435..5a118a9 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -109,8 +109,7 @@
return name_out;
}
-bool ParseResourceName(const StringPiece& str, ResourceNameRef* out_ref,
- bool* out_private) {
+bool ParseResourceName(StringPiece str, ResourceNameRef* out_ref, bool* out_private) {
if (str.empty()) {
return false;
}
@@ -151,8 +150,8 @@
return true;
}
-bool ParseReference(const StringPiece& str, ResourceNameRef* out_ref,
- bool* out_create, bool* out_private) {
+bool ParseReference(StringPiece str, ResourceNameRef* out_ref, bool* out_create,
+ bool* out_private) {
StringPiece trimmed_str(util::TrimWhitespace(str));
if (trimmed_str.empty()) {
return false;
@@ -198,11 +197,11 @@
return false;
}
-bool IsReference(const StringPiece& str) {
+bool IsReference(StringPiece str) {
return ParseReference(str, nullptr, nullptr, nullptr);
}
-bool ParseAttributeReference(const StringPiece& str, ResourceNameRef* out_ref) {
+bool ParseAttributeReference(StringPiece str, ResourceNameRef* out_ref) {
StringPiece trimmed_str(util::TrimWhitespace(str));
if (trimmed_str.empty()) {
return false;
@@ -235,7 +234,7 @@
return false;
}
-bool IsAttributeReference(const StringPiece& str) {
+bool IsAttributeReference(StringPiece str) {
return ParseAttributeReference(str, nullptr);
}
@@ -247,7 +246,7 @@
* <[*]package>:[style/]<entry>
* [[*]package:style/]<entry>
*/
-std::optional<Reference> ParseStyleParentReference(const StringPiece& str, std::string* out_error) {
+std::optional<Reference> ParseStyleParentReference(StringPiece str, std::string* out_error) {
if (str.empty()) {
return {};
}
@@ -296,7 +295,7 @@
return result;
}
-std::optional<Reference> ParseXmlAttributeName(const StringPiece& str) {
+std::optional<Reference> ParseXmlAttributeName(StringPiece str) {
StringPiece trimmed_str = util::TrimWhitespace(str);
const char* start = trimmed_str.data();
const char* const end = start + trimmed_str.size();
@@ -325,8 +324,7 @@
return std::optional<Reference>(std::move(ref));
}
-std::unique_ptr<Reference> TryParseReference(const StringPiece& str,
- bool* out_create) {
+std::unique_ptr<Reference> TryParseReference(StringPiece str, bool* out_create) {
ResourceNameRef ref;
bool private_ref = false;
if (ParseReference(str, &ref, out_create, &private_ref)) {
@@ -344,7 +342,7 @@
return {};
}
-std::unique_ptr<Item> TryParseNullOrEmpty(const StringPiece& str) {
+std::unique_ptr<Item> TryParseNullOrEmpty(StringPiece str) {
const StringPiece trimmed_str(util::TrimWhitespace(str));
if (trimmed_str == "@null") {
return MakeNull();
@@ -365,8 +363,7 @@
android::Res_value::DATA_NULL_EMPTY);
}
-std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
- const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, StringPiece str) {
StringPiece trimmed_str(util::TrimWhitespace(str));
for (const Attribute::Symbol& symbol : enum_attr->symbols) {
// Enum symbols are stored as @package:id/symbol resources,
@@ -382,8 +379,7 @@
return {};
}
-std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr,
- const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* flag_attr, StringPiece str) {
android::Res_value flags = {};
flags.dataType = android::Res_value::TYPE_INT_HEX;
flags.data = 0u;
@@ -393,7 +389,7 @@
return util::make_unique<BinaryPrimitive>(flags);
}
- for (const StringPiece& part : util::Tokenize(str, '|')) {
+ for (StringPiece part : util::Tokenize(str, '|')) {
StringPiece trimmed_part = util::TrimWhitespace(part);
bool flag_set = false;
@@ -429,7 +425,7 @@
}
}
-std::unique_ptr<BinaryPrimitive> TryParseColor(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseColor(StringPiece str) {
StringPiece color_str(util::TrimWhitespace(str));
const char* start = color_str.data();
const size_t len = color_str.size();
@@ -484,7 +480,7 @@
: util::make_unique<BinaryPrimitive>(value);
}
-std::optional<bool> ParseBool(const StringPiece& str) {
+std::optional<bool> ParseBool(StringPiece str) {
StringPiece trimmed_str(util::TrimWhitespace(str));
if (trimmed_str == "true" || trimmed_str == "TRUE" || trimmed_str == "True") {
return std::optional<bool>(true);
@@ -495,7 +491,7 @@
return {};
}
-std::optional<uint32_t> ParseInt(const StringPiece& str) {
+std::optional<uint32_t> ParseInt(StringPiece str) {
std::u16string str16 = android::util::Utf8ToUtf16(str);
android::Res_value value;
if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -504,7 +500,7 @@
return {};
}
-std::optional<ResourceId> ParseResourceId(const StringPiece& str) {
+std::optional<ResourceId> ParseResourceId(StringPiece str) {
StringPiece trimmed_str(util::TrimWhitespace(str));
std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -520,7 +516,7 @@
return {};
}
-std::optional<int> ParseSdkVersion(const StringPiece& str) {
+std::optional<int> ParseSdkVersion(StringPiece str) {
StringPiece trimmed_str(util::TrimWhitespace(str));
std::u16string str16 = android::util::Utf8ToUtf16(trimmed_str);
@@ -539,14 +535,14 @@
const StringPiece::const_iterator begin = std::begin(trimmed_str);
const StringPiece::const_iterator end = std::end(trimmed_str);
const StringPiece::const_iterator codename_end = std::find(begin, end, '.');
- entry = GetDevelopmentSdkCodeNameVersion(trimmed_str.substr(begin, codename_end));
+ entry = GetDevelopmentSdkCodeNameVersion(StringPiece(begin, codename_end - begin));
if (entry) {
return entry.value();
}
return {};
}
-std::unique_ptr<BinaryPrimitive> TryParseBool(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseBool(StringPiece str) {
if (std::optional<bool> maybe_result = ParseBool(str)) {
const uint32_t data = maybe_result.value() ? 0xffffffffu : 0u;
return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, data);
@@ -559,7 +555,7 @@
val ? 0xffffffffu : 0u);
}
-std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseInt(StringPiece str) {
std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
android::Res_value value;
if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
@@ -572,7 +568,7 @@
return util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_DEC, val);
}
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) {
+std::unique_ptr<BinaryPrimitive> TryParseFloat(StringPiece str) {
std::u16string str16 = android::util::Utf8ToUtf16(util::TrimWhitespace(str));
android::Res_value value;
if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) {
@@ -623,7 +619,7 @@
}
std::unique_ptr<Item> TryParseItemForAttribute(
- const StringPiece& value, uint32_t type_mask,
+ StringPiece value, uint32_t type_mask,
const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
@@ -687,7 +683,7 @@
* allows.
*/
std::unique_ptr<Item> TryParseItemForAttribute(
- const StringPiece& str, const Attribute* attr,
+ StringPiece str, const Attribute* attr,
const std::function<bool(const ResourceName&)>& on_create_reference) {
using android::ResTable_map;
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 22cf345..f30f4ac 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -38,7 +38,7 @@
* `out_resource` set to the parsed resource name and `out_private` set to true
* if a '*' prefix was present.
*/
-bool ParseResourceName(const android::StringPiece& str, ResourceNameRef* out_resource,
+bool ParseResourceName(android::StringPiece str, ResourceNameRef* out_resource,
bool* out_private = nullptr);
/*
@@ -49,27 +49,27 @@
* If '+' was present in the reference, `out_create` is set to true.
* If '*' was present in the reference, `out_private` is set to true.
*/
-bool ParseReference(const android::StringPiece& str, ResourceNameRef* out_reference,
+bool ParseReference(android::StringPiece str, ResourceNameRef* out_reference,
bool* out_create = nullptr, bool* out_private = nullptr);
/*
* Returns true if the string is in the form of a resource reference
* (@[+][package:]type/name).
*/
-bool IsReference(const android::StringPiece& str);
+bool IsReference(android::StringPiece str);
/*
* Returns true if the string was parsed as an attribute reference
* (?[package:][type/]name),
* with `out_reference` set to the parsed reference.
*/
-bool ParseAttributeReference(const android::StringPiece& str, ResourceNameRef* out_reference);
+bool ParseAttributeReference(android::StringPiece str, ResourceNameRef* out_reference);
/**
* Returns true if the string is in the form of an attribute
* reference(?[package:][type/]name).
*/
-bool IsAttributeReference(const android::StringPiece& str);
+bool IsAttributeReference(android::StringPiece str);
/**
* Convert an android::ResTable::resource_name to an aapt::ResourceName struct.
@@ -85,22 +85,22 @@
* Returns a boolean value if the string is equal to TRUE, true, True, FALSE,
* false, or False.
*/
-std::optional<bool> ParseBool(const android::StringPiece& str);
+std::optional<bool> ParseBool(android::StringPiece str);
/**
* Returns a uint32_t if the string is an integer.
*/
-std::optional<uint32_t> ParseInt(const android::StringPiece& str);
+std::optional<uint32_t> ParseInt(android::StringPiece str);
/**
* Returns an ID if it the string represented a valid ID.
*/
-std::optional<ResourceId> ParseResourceId(const android::StringPiece& str);
+std::optional<ResourceId> ParseResourceId(android::StringPiece str);
/**
* Parses an SDK version, which can be an integer, or a letter from A-Z.
*/
-std::optional<int> ParseSdkVersion(const android::StringPiece& str);
+std::optional<int> ParseSdkVersion(android::StringPiece str);
/*
* Returns a Reference, or None Maybe instance if the string `str` was parsed as
@@ -113,7 +113,7 @@
* ?[package:]style/<entry> or
* <package>:[style/]<entry>
*/
-std::optional<Reference> ParseStyleParentReference(const android::StringPiece& str,
+std::optional<Reference> ParseStyleParentReference(android::StringPiece str,
std::string* out_error);
/*
@@ -123,7 +123,7 @@
*
* package:entry
*/
-std::optional<Reference> ParseXmlAttributeName(const android::StringPiece& str);
+std::optional<Reference> ParseXmlAttributeName(android::StringPiece str);
/*
* Returns a Reference object if the string was parsed as a resource or
@@ -132,14 +132,13 @@
* if
* the '+' was present in the string.
*/
-std::unique_ptr<Reference> TryParseReference(const android::StringPiece& str,
- bool* out_create = nullptr);
+std::unique_ptr<Reference> TryParseReference(android::StringPiece str, bool* out_create = nullptr);
/*
* Returns a BinaryPrimitve object representing @null or @empty if the string
* was parsed as one.
*/
-std::unique_ptr<Item> TryParseNullOrEmpty(const android::StringPiece& str);
+std::unique_ptr<Item> TryParseNullOrEmpty(android::StringPiece str);
// Returns a Reference representing @null.
// Due to runtime compatibility issues, this is encoded as a reference with ID 0.
@@ -154,13 +153,13 @@
* Returns a BinaryPrimitve object representing a color if the string was parsed
* as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseColor(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseColor(android::StringPiece str);
/*
* Returns a BinaryPrimitve object representing a boolean if the string was
* parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseBool(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseBool(android::StringPiece str);
// Returns a boolean BinaryPrimitive.
std::unique_ptr<BinaryPrimitive> MakeBool(bool val);
@@ -169,7 +168,7 @@
* Returns a BinaryPrimitve object representing an integer if the string was
* parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseInt(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseInt(android::StringPiece str);
// Returns an integer BinaryPrimitive.
std::unique_ptr<BinaryPrimitive> MakeInt(uint32_t value);
@@ -178,21 +177,21 @@
* Returns a BinaryPrimitve object representing a floating point number
* (float, dimension, etc) if the string was parsed as one.
*/
-std::unique_ptr<BinaryPrimitive> TryParseFloat(const android::StringPiece& str);
+std::unique_ptr<BinaryPrimitive> TryParseFloat(android::StringPiece str);
/*
* Returns a BinaryPrimitve object representing an enum symbol if the string was
* parsed as one.
*/
std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr,
- const android::StringPiece& str);
+ android::StringPiece str);
/*
* Returns a BinaryPrimitve object representing a flag symbol if the string was
* parsed as one.
*/
std::unique_ptr<BinaryPrimitive> TryParseFlagSymbol(const Attribute* enum_attr,
- const android::StringPiece& str);
+ android::StringPiece str);
/*
* Try to convert a string to an Item for the given attribute. The attribute
* will
@@ -201,11 +200,11 @@
* reference to an ID that must be created (@+id/foo).
*/
std::unique_ptr<Item> TryParseItemForAttribute(
- const android::StringPiece& value, const Attribute* attr,
+ android::StringPiece value, const Attribute* attr,
const std::function<bool(const ResourceName&)>& on_create_reference = {});
std::unique_ptr<Item> TryParseItemForAttribute(
- const android::StringPiece& value, uint32_t type_mask,
+ android::StringPiece value, uint32_t type_mask,
const std::function<bool(const ResourceName&)>& on_create_reference = {});
uint32_t AndroidTypeToAttributeTypeMask(uint16_t type);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index c4d54be..a5754e0 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -206,7 +206,7 @@
PrettyPrintReferenceImpl(*this, true /*print_package*/, printer);
}
-void Reference::PrettyPrint(const StringPiece& package, Printer* printer) const {
+void Reference::PrettyPrint(StringPiece package, Printer* printer) const {
const bool print_package = name ? package != name.value().package : true;
PrettyPrintReferenceImpl(*this, print_package, printer);
}
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f5167a1..6f9dccb 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -83,8 +83,8 @@
return comment_;
}
- void SetComment(const android::StringPiece& str) {
- comment_ = str.to_string();
+ void SetComment(android::StringPiece str) {
+ comment_.assign(str);
}
void SetComment(std::string&& str) {
@@ -176,7 +176,7 @@
void PrettyPrint(text::Printer* printer) const override;
// Prints the reference without a package name if the package name matches the one given.
- void PrettyPrint(const android::StringPiece& package, text::Printer* printer) const;
+ void PrettyPrint(android::StringPiece package, text::Printer* printer) const;
};
bool operator<(const Reference&, const Reference&);
diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp
index 34e8edb..a7c5479 100644
--- a/tools/aapt2/SdkConstants.cpp
+++ b/tools/aapt2/SdkConstants.cpp
@@ -77,7 +77,7 @@
return iter->second;
}
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const StringPiece& code_name) {
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(StringPiece code_name) {
return (sDevelopmentSdkCodeNames.find(code_name) == sDevelopmentSdkCodeNames.end())
? std::optional<ApiVersion>()
: sDevelopmentSdkLevel;
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 0bd61c0..40bcef7 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -63,7 +63,7 @@
};
ApiVersion FindAttributeSdkLevel(const ResourceId& id);
-std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(const android::StringPiece& code_name);
+std::optional<ApiVersion> GetDevelopmentSdkCodeNameVersion(android::StringPiece code_name);
} // namespace aapt
diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp
index 697b110..3c0831c 100644
--- a/tools/aapt2/cmd/ApkInfo.cpp
+++ b/tools/aapt2/cmd/ApkInfo.cpp
@@ -64,7 +64,7 @@
Usage(&std::cerr);
return 1;
}
- const StringPiece& path = args[0];
+ StringPiece path = args[0];
std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_);
if (!apk) {
return 1;
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index b1452fa..514651e 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -33,7 +33,7 @@
namespace aapt {
-std::string GetSafePath(const StringPiece& arg) {
+std::string GetSafePath(StringPiece arg) {
#ifdef _WIN32
// If the path exceeds the maximum path length for Windows, encode the path using the
// extended-length prefix
@@ -47,63 +47,62 @@
return path8;
#else
- return arg.to_string();
+ return std::string(arg);
#endif
}
-void Command::AddRequiredFlag(const StringPiece& name, const StringPiece& description,
- std::string* value, uint32_t flags) {
- auto func = [value, flags](const StringPiece& arg) -> bool {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
+ uint32_t flags) {
+ auto func = [value, flags](StringPiece arg) -> bool {
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
}
-void Command::AddRequiredFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
- auto func = [value, flags](const StringPiece& arg) -> bool {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+ auto func = [value, flags](StringPiece arg) -> bool {
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
}
-void Command::AddOptionalFlag(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlag(StringPiece name, StringPiece description,
std::optional<std::string>* value, uint32_t flags) {
- auto func = [value, flags](const StringPiece& arg) -> bool {
- *value = (flags & Command::kPath) ? GetSafePath(arg) : arg.to_string();
+ auto func = [value, flags](StringPiece arg) -> bool {
+ *value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
}
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
- auto func = [value, flags](const StringPiece& arg) -> bool {
- value->push_back((flags & Command::kPath) ? GetSafePath(arg) : arg.to_string());
+ auto func = [value, flags](StringPiece arg) -> bool {
+ value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
}
-void Command::AddOptionalFlagList(const StringPiece& name, const StringPiece& description,
+void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::unordered_set<std::string>* value) {
- auto func = [value](const StringPiece& arg) -> bool {
- value->insert(arg.to_string());
+ auto func = [value](StringPiece arg) -> bool {
+ value->emplace(arg);
return true;
};
flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
}
-void Command::AddOptionalSwitch(const StringPiece& name, const StringPiece& description,
- bool* value) {
- auto func = [value](const StringPiece& arg) -> bool {
+void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
+ auto func = [value](StringPiece arg) -> bool {
*value = true;
return true;
};
@@ -120,8 +119,8 @@
}
}
-void Command::SetDescription(const StringPiece& description) {
- description_ = description.to_string();
+void Command::SetDescription(StringPiece description) {
+ description_ = std::string(description);
}
void Command::Usage(std::ostream* out) {
@@ -183,7 +182,7 @@
std::vector<std::string> file_args;
for (size_t i = 0; i < args.size(); i++) {
- const StringPiece& arg = args[i];
+ StringPiece arg = args[i];
if (*(arg.data()) != '-') {
// Continue parsing as the subcommand if the first argument matches one of the subcommands
if (i == 0) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 8678cda..1416e98 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -30,13 +30,10 @@
class Command {
public:
- explicit Command(const android::StringPiece& name)
- : name_(name.to_string()), full_subcommand_name_(name.to_string()){};
+ explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
- explicit Command(const android::StringPiece& name, const android::StringPiece& short_name)
- : name_(name.to_string()),
- short_name_(short_name.to_string()),
- full_subcommand_name_(name.to_string()){};
+ explicit Command(android::StringPiece name, android::StringPiece short_name)
+ : name_(name), short_name_(short_name), full_subcommand_name_(name){};
Command(Command&&) = default;
Command& operator=(Command&&) = default;
@@ -52,30 +49,26 @@
kPath = 1 << 0,
};
- void AddRequiredFlag(const android::StringPiece& name, const android::StringPiece& description,
+ void AddRequiredFlag(android::StringPiece name, android::StringPiece description,
std::string* value, uint32_t flags = 0);
- void AddRequiredFlagList(const android::StringPiece& name,
- const android::StringPiece& description, std::vector<std::string>* value,
- uint32_t flags = 0);
+ void AddRequiredFlagList(android::StringPiece name, android::StringPiece description,
+ std::vector<std::string>* value, uint32_t flags = 0);
- void AddOptionalFlag(const android::StringPiece& name, const android::StringPiece& description,
+ void AddOptionalFlag(android::StringPiece name, android::StringPiece description,
std::optional<std::string>* value, uint32_t flags = 0);
- void AddOptionalFlagList(const android::StringPiece& name,
- const android::StringPiece& description, std::vector<std::string>* value,
- uint32_t flags = 0);
+ void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
+ std::vector<std::string>* value, uint32_t flags = 0);
- void AddOptionalFlagList(const android::StringPiece& name,
- const android::StringPiece& description,
+ void AddOptionalFlagList(android::StringPiece name, android::StringPiece description,
std::unordered_set<std::string>* value);
- void AddOptionalSwitch(const android::StringPiece& name, const android::StringPiece& description,
- bool* value);
+ void AddOptionalSwitch(android::StringPiece name, android::StringPiece description, bool* value);
void AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental = false);
- void SetDescription(const android::StringPiece& name);
+ void SetDescription(android::StringPiece name);
// Prints the help menu of the command.
void Usage(std::ostream* out);
@@ -90,17 +83,21 @@
private:
struct Flag {
- explicit Flag(const android::StringPiece& name, const android::StringPiece& description,
+ explicit Flag(android::StringPiece name, android::StringPiece description,
const bool is_required, const size_t num_args,
- std::function<bool(const android::StringPiece& value)>&& action)
- : name(name.to_string()), description(description.to_string()), is_required(is_required),
- num_args(num_args), action(std::move(action)) {}
+ std::function<bool(android::StringPiece value)>&& action)
+ : name(name),
+ description(description),
+ is_required(is_required),
+ num_args(num_args),
+ action(std::move(action)) {
+ }
const std::string name;
const std::string description;
const bool is_required;
const size_t num_args;
- const std::function<bool(const android::StringPiece& value)> action;
+ const std::function<bool(android::StringPiece value)> action;
bool found = false;
};
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 0409f73..03f9715 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -125,8 +125,12 @@
const android::Source res_path =
options.source_path ? StringPiece(options.source_path.value()) : StringPiece(path);
- return ResourcePathData{res_path, dir_str.to_string(), name.to_string(),
- extension.to_string(), config_str.to_string(), config};
+ return ResourcePathData{res_path,
+ std::string(dir_str),
+ std::string(name),
+ std::string(extension),
+ std::string(config_str),
+ config};
}
static std::string BuildIntermediateContainerFilename(const ResourcePathData& data) {
@@ -279,7 +283,7 @@
return true;
}
-static bool WriteHeaderAndDataToWriter(const StringPiece& output_path, const ResourceFile& file,
+static bool WriteHeaderAndDataToWriter(StringPiece output_path, const ResourceFile& file,
io::KnownSizeInputStream* in, IArchiveWriter* writer,
android::IDiagnostics* diag) {
TRACE_CALL();
@@ -311,7 +315,7 @@
return true;
}
-static bool FlattenXmlToOutStream(const StringPiece& output_path, const xml::XmlResource& xmlres,
+static bool FlattenXmlToOutStream(StringPiece output_path, const xml::XmlResource& xmlres,
ContainerWriter* container_writer, android::IDiagnostics* diag) {
pb::internal::CompiledFile pb_compiled_file;
SerializeCompiledFileToPb(xmlres.file, &pb_compiled_file);
@@ -538,7 +542,7 @@
if (context->IsVerbose()) {
// For debugging only, use the legacy PNG cruncher and compare the resulting file sizes.
// This will help catch exotic cases where the new code may generate larger PNGs.
- std::stringstream legacy_stream(content.to_string());
+ std::stringstream legacy_stream{std::string(content)};
android::BigBuffer legacy_buffer(4096);
Png png(context->GetDiagnostics());
if (!png.process(path_data.source, &legacy_stream, &legacy_buffer, {})) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 52e113e..612e3a6 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -387,7 +387,7 @@
}
Context context;
- const StringPiece& path = args[0];
+ StringPiece path = args[0];
unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
if (apk == nullptr) {
context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 423e939..5bfc732 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -78,7 +78,7 @@
SymbolTable symbol_table_;
};
-static void EmitDiffLine(const android::Source& source, const StringPiece& message) {
+static void EmitDiffLine(const android::Source& source, StringPiece message) {
std::cerr << source << ": " << message << "\n";
}
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index a8d2299..97404fc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -126,8 +126,8 @@
return compilation_package_;
}
- void SetCompilationPackage(const StringPiece& package_name) {
- compilation_package_ = package_name.to_string();
+ void SetCompilationPackage(StringPiece package_name) {
+ compilation_package_ = std::string(package_name);
}
uint8_t GetPackageId() override {
@@ -240,9 +240,9 @@
IAaptContext* context_;
};
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res,
- const StringPiece& path, bool keep_raw_values, bool utf16,
- OutputFormat format, IArchiveWriter* writer) {
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, StringPiece path,
+ bool keep_raw_values, bool utf16, OutputFormat format,
+ IArchiveWriter* writer) {
TRACE_CALL();
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(android::DiagMessage(path)
@@ -262,8 +262,8 @@
}
io::BigBufferInputStream input_stream(&buffer);
- return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(),
- ArchiveEntry::kCompress, writer);
+ return io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kCompress,
+ writer);
} break;
case OutputFormat::kProto: {
@@ -272,8 +272,7 @@
SerializeXmlOptions options;
options.remove_empty_text_nodes = (path == kAndroidManifestPath);
SerializeXmlResourceToPb(xml_res, &pb_node);
- return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress,
- writer);
+ return io::CopyProtoToArchive(context, &pb_node, path, ArchiveEntry::kCompress, writer);
} break;
}
return false;
@@ -329,13 +328,13 @@
};
template <typename T>
-uint32_t GetCompressionFlags(const StringPiece& str, T options) {
+uint32_t GetCompressionFlags(StringPiece str, T options) {
if (options.do_not_compress_anything) {
return 0;
}
- if (options.regex_to_not_compress
- && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) {
+ if (options.regex_to_not_compress &&
+ std::regex_search(str.begin(), str.end(), options.regex_to_not_compress.value())) {
return 0;
}
@@ -1176,7 +1175,7 @@
return bcp47tag;
}
- std::unique_ptr<IArchiveWriter> MakeArchiveWriter(const StringPiece& out) {
+ std::unique_ptr<IArchiveWriter> MakeArchiveWriter(StringPiece out) {
if (options_.output_to_directory) {
return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out);
} else {
@@ -1212,8 +1211,8 @@
return false;
}
- bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate,
- const StringPiece& out_package, const JavaClassGeneratorOptions& java_options,
+ bool WriteJavaFile(ResourceTable* table, StringPiece package_name_to_generate,
+ StringPiece out_package, const JavaClassGeneratorOptions& java_options,
const std::optional<std::string>& out_text_symbols_path = {}) {
if (!options_.generate_java_class_path && !out_text_symbols_path) {
return true;
@@ -2473,14 +2472,14 @@
for (std::string& extra_package : extra_java_packages_) {
// A given package can actually be a colon separated list of packages.
for (StringPiece package : util::Split(extra_package, ':')) {
- options_.extra_java_packages.insert(package.to_string());
+ options_.extra_java_packages.emplace(package);
}
}
if (product_list_) {
for (StringPiece product : util::Tokenize(product_list_.value(), ',')) {
if (product != "" && product != "default") {
- options_.products.insert(product.to_string());
+ options_.products.emplace(product);
}
}
}
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 042926c..d7a39bf 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -154,13 +154,22 @@
return 1;
}
- if (options_.shorten_resource_paths) {
- Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+ Obfuscator obfuscator(options_);
+ if (obfuscator.IsEnabled()) {
if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed shortening resource paths");
return 1;
}
+
+ if (options_.obfuscation_map_path &&
+ !obfuscator.WriteObfuscationMap(options_.obfuscation_map_path.value())) {
+ context_->GetDiagnostics()->Error(android::DiagMessage()
+ << "failed to write the obfuscation map to file");
+ return 1;
+ }
+
+ // TODO(b/246489170): keep the old option and format until transform to the new one
if (options_.shortened_paths_map_path
&& !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map,
options_.shortened_paths_map_path.value())) {
@@ -292,6 +301,7 @@
ArchiveEntry::kAlign, writer);
}
+ // TODO(b/246489170): keep the old option and format until transform to the new one
bool WriteShortenedPathsMap(const std::map<std::string, std::string> &path_map,
const std::string &file_path) {
std::stringstream ss;
@@ -370,8 +380,8 @@
if (!kept_artifacts_.empty()) {
for (const std::string& artifact_str : kept_artifacts_) {
- for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
- options_.kept_artifacts.insert(artifact.to_string());
+ for (StringPiece artifact : util::Tokenize(artifact_str, ',')) {
+ options_.kept_artifacts.emplace(artifact);
}
}
}
@@ -403,7 +413,7 @@
if (target_densities_) {
// Parse the target screen densities.
- for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) {
+ for (StringPiece config_str : util::Tokenize(target_densities_.value(), ',')) {
std::optional<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
if (!target_density) {
return 1;
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 794a87b..1879f25 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -58,6 +58,7 @@
bool shorten_resource_paths = false;
// Path to the output map of original resource paths to shortened paths.
+ // TODO(b/246489170): keep the old option and format until transform to the new one
std::optional<std::string> shortened_paths_map_path;
// Whether sparse encoding should be used for O+ resources.
@@ -65,6 +66,9 @@
// Whether sparse encoding should be used for all resources.
bool force_sparse_encoding = false;
+
+ // Path to the output map of original resource paths/names to obfuscated paths/names.
+ std::optional<std::string> obfuscation_map_path;
};
class OptimizeCommand : public Command {
@@ -120,9 +124,13 @@
AddOptionalSwitch("--shorten-resource-paths",
"Shortens the paths of resources inside the APK.",
&options_.shorten_resource_paths);
+ // TODO(b/246489170): keep the old option and format until transform to the new one
AddOptionalFlag("--resource-path-shortening-map",
- "Path to output the map of old resource paths to shortened paths.",
- &options_.shortened_paths_map_path);
+ "[Deprecated]Path to output the map of old resource paths to shortened paths.",
+ &options_.shortened_paths_map_path);
+ AddOptionalFlag("--save-obfuscation-map",
+ "Path to output the map of original paths/names to obfuscated paths/names.",
+ &options_.obfuscation_map_path);
AddOptionalSwitch(
"--deduplicate-entry-values",
"Whether to deduplicate pairs of resource entry and value for simple resources.\n"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 56e2f52..92849cf 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -34,8 +34,7 @@
namespace aapt {
-std::optional<uint16_t> ParseTargetDensityParameter(const StringPiece& arg,
- android::IDiagnostics* diag) {
+std::optional<uint16_t> ParseTargetDensityParameter(StringPiece arg, android::IDiagnostics* diag) {
ConfigDescription preferred_density_config;
if (!ConfigDescription::Parse(arg, &preferred_density_config)) {
diag->Error(android::DiagMessage()
@@ -55,7 +54,7 @@
return preferred_density_config.density;
}
-bool ParseSplitParameter(const StringPiece& arg, android::IDiagnostics* diag, std::string* out_path,
+bool ParseSplitParameter(StringPiece arg, android::IDiagnostics* diag, std::string* out_path,
SplitConstraints* out_split) {
CHECK(diag != nullptr);
CHECK(out_path != nullptr);
@@ -77,7 +76,7 @@
*out_path = parts[0];
out_split->name = parts[1];
- for (const StringPiece& config_str : util::Tokenize(parts[1], ',')) {
+ for (StringPiece config_str : util::Tokenize(parts[1], ',')) {
ConfigDescription config;
if (!ConfigDescription::Parse(config_str, &config)) {
diag->Error(android::DiagMessage()
@@ -93,7 +92,7 @@
android::IDiagnostics* diag) {
std::unique_ptr<AxisConfigFilter> filter = util::make_unique<AxisConfigFilter>();
for (const std::string& config_arg : args) {
- for (const StringPiece& config_str : util::Tokenize(config_arg, ',')) {
+ for (StringPiece config_str : util::Tokenize(config_arg, ',')) {
ConfigDescription config;
LocaleValue lv;
if (lv.InitFromFilterString(config_str)) {
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 3d4ca24..169d5f9 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -34,13 +34,13 @@
// Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
// Returns Nothing and logs a human friendly error message if the string was not legal.
-std::optional<uint16_t> ParseTargetDensityParameter(const android::StringPiece& arg,
+std::optional<uint16_t> ParseTargetDensityParameter(android::StringPiece arg,
android::IDiagnostics* diag);
// Parses a string of the form 'path/to/output.apk:<config>[,<config>...]' and fills in
// `out_path` with the path and `out_split` with the set of ConfigDescriptions.
// Returns false and logs a human friendly error message if the string was not legal.
-bool ParseSplitParameter(const android::StringPiece& arg, android::IDiagnostics* diag,
+bool ParseSplitParameter(android::StringPiece arg, android::IDiagnostics* diag,
std::string* out_path, SplitConstraints* out_split);
// Parses a set of config filter strings of the form 'en,fr-rFR' and returns an IConfigFilter.
diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp
index c931da4..4538ecc 100644
--- a/tools/aapt2/compile/NinePatch.cpp
+++ b/tools/aapt2/compile/NinePatch.cpp
@@ -218,11 +218,9 @@
static bool PopulateBounds(const std::vector<Range>& padding,
const std::vector<Range>& layout_bounds,
- const std::vector<Range>& stretch_regions,
- const int32_t length, int32_t* padding_start,
- int32_t* padding_end, int32_t* layout_start,
- int32_t* layout_end, const StringPiece& edge_name,
- std::string* out_err) {
+ const std::vector<Range>& stretch_regions, const int32_t length,
+ int32_t* padding_start, int32_t* padding_end, int32_t* layout_start,
+ int32_t* layout_end, StringPiece edge_name, std::string* out_err) {
if (padding.size() > 1) {
std::stringstream err_stream;
err_stream << "too many padding sections on " << edge_name << " border";
diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h
index 7f8d923..a8b7dd1 100644
--- a/tools/aapt2/compile/Png.h
+++ b/tools/aapt2/compile/Png.h
@@ -59,7 +59,7 @@
*/
class PngChunkFilter : public io::InputStream {
public:
- explicit PngChunkFilter(const android::StringPiece& data);
+ explicit PngChunkFilter(android::StringPiece data);
virtual ~PngChunkFilter() = default;
bool Next(const void** buffer, size_t* len) override;
diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp
index 4db2392..2e55d0c 100644
--- a/tools/aapt2/compile/PngChunkFilter.cpp
+++ b/tools/aapt2/compile/PngChunkFilter.cpp
@@ -70,7 +70,7 @@
}
}
-PngChunkFilter::PngChunkFilter(const StringPiece& data) : data_(data) {
+PngChunkFilter::PngChunkFilter(StringPiece data) : data_(data) {
if (util::StartsWith(data_, kPngSignature)) {
window_start_ = 0;
window_end_ = kPngSignatureSize;
diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp
index 3a515fa..463ce78 100644
--- a/tools/aapt2/compile/Pseudolocalizer.cpp
+++ b/tools/aapt2/compile/Pseudolocalizer.cpp
@@ -20,36 +20,42 @@
using android::StringPiece;
+using namespace std::literals;
+
namespace aapt {
// String basis to generate expansion
-static const std::string kExpansionString =
+static constexpr auto kExpansionString =
"one two three "
"four five six seven eight nine ten eleven twelve thirteen "
- "fourteen fiveteen sixteen seventeen nineteen twenty";
+ "fourteen fiveteen sixteen seventeen nineteen twenty"sv;
// Special unicode characters to override directionality of the words
-static const std::string kRlm = "\u200f";
-static const std::string kRlo = "\u202e";
-static const std::string kPdf = "\u202c";
+static constexpr auto kRlm = "\u200f"sv;
+static constexpr auto kRlo = "\u202e"sv;
+static constexpr auto kPdf = "\u202c"sv;
// Placeholder marks
-static const std::string kPlaceholderOpen = "\u00bb";
-static const std::string kPlaceholderClose = "\u00ab";
+static constexpr auto kPlaceholderOpen = "\u00bb"sv;
+static constexpr auto kPlaceholderClose = "\u00ab"sv;
static const char kArgStart = '{';
static const char kArgEnd = '}';
class PseudoMethodNone : public PseudoMethodImpl {
public:
- std::string Text(const StringPiece& text) override { return text.to_string(); }
- std::string Placeholder(const StringPiece& text) override { return text.to_string(); }
+ std::string Text(StringPiece text) override {
+ return std::string(text);
+ }
+ std::string Placeholder(StringPiece text) override {
+ return std::string(text);
+ }
};
class PseudoMethodBidi : public PseudoMethodImpl {
public:
- std::string Text(const StringPiece& text) override;
- std::string Placeholder(const StringPiece& text) override;
+ std::string Text(StringPiece text) override;
+ std::string Placeholder(StringPiece text) override;
};
class PseudoMethodAccent : public PseudoMethodImpl {
@@ -57,8 +63,8 @@
PseudoMethodAccent() : depth_(0), word_count_(0), length_(0) {}
std::string Start() override;
std::string End() override;
- std::string Text(const StringPiece& text) override;
- std::string Placeholder(const StringPiece& text) override;
+ std::string Text(StringPiece text) override;
+ std::string Placeholder(StringPiece text) override;
private:
size_t depth_;
@@ -84,7 +90,7 @@
}
}
-std::string Pseudolocalizer::Text(const StringPiece& text) {
+std::string Pseudolocalizer::Text(StringPiece text) {
std::string out;
size_t depth = last_depth_;
size_t lastpos, pos;
@@ -116,7 +122,7 @@
}
size_t size = nextpos - lastpos;
if (size) {
- std::string chunk = text.substr(lastpos, size).to_string();
+ std::string chunk(text.substr(lastpos, size));
if (pseudo) {
chunk = impl_->Text(chunk);
} else if (str[lastpos] == kArgStart && str[nextpos - 1] == kArgEnd) {
@@ -301,21 +307,23 @@
}
static std::string PseudoGenerateExpansion(const unsigned int length) {
- std::string result = kExpansionString;
- const char* s = result.data();
+ std::string result(kExpansionString);
if (result.size() < length) {
result += " ";
result += PseudoGenerateExpansion(length - result.size());
} else {
int ext = 0;
// Should contain only whole words, so looking for a space
- for (unsigned int i = length + 1; i < result.size(); ++i) {
- ++ext;
- if (s[i] == ' ') {
- break;
+ {
+ const char* const s = result.data();
+ for (unsigned int i = length + 1; i < result.size(); ++i) {
+ ++ext;
+ if (s[i] == ' ') {
+ break;
+ }
}
}
- result = result.substr(0, length + ext);
+ result.resize(length + ext);
}
return result;
}
@@ -349,7 +357,7 @@
*
* Note: This leaves placeholder syntax untouched.
*/
-std::string PseudoMethodAccent::Text(const StringPiece& source) {
+std::string PseudoMethodAccent::Text(StringPiece source) {
const char* s = source.data();
std::string result;
const size_t I = source.size();
@@ -435,12 +443,12 @@
return result;
}
-std::string PseudoMethodAccent::Placeholder(const StringPiece& source) {
+std::string PseudoMethodAccent::Placeholder(StringPiece source) {
// Surround a placeholder with brackets
- return kPlaceholderOpen + source.to_string() + kPlaceholderClose;
+ return (std::string(kPlaceholderOpen) += source) += kPlaceholderClose;
}
-std::string PseudoMethodBidi::Text(const StringPiece& source) {
+std::string PseudoMethodBidi::Text(StringPiece source) {
const char* s = source.data();
std::string result;
bool lastspace = true;
@@ -456,10 +464,10 @@
space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't'));
if (lastspace && !space) {
// Word start
- result += kRlm + kRlo;
+ (result += kRlm) += kRlo;
} else if (!lastspace && space) {
// Word end
- result += kPdf + kRlm;
+ (result += kPdf) += kRlm;
}
lastspace = space;
if (escape) {
@@ -470,14 +478,14 @@
}
if (!lastspace) {
// End of last word
- result += kPdf + kRlm;
+ (result += kPdf) += kRlm;
}
return result;
}
-std::string PseudoMethodBidi::Placeholder(const StringPiece& source) {
+std::string PseudoMethodBidi::Placeholder(StringPiece source) {
// Surround a placeholder with directionality change sequence
- return kRlm + kRlo + source.to_string() + kPdf + kRlm;
+ return (((std::string(kRlm) += kRlo) += source) += kPdf) += kRlm;
}
} // namespace aapt
diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h
index 4dedc70..2b94bcc 100644
--- a/tools/aapt2/compile/Pseudolocalizer.h
+++ b/tools/aapt2/compile/Pseudolocalizer.h
@@ -31,8 +31,8 @@
virtual ~PseudoMethodImpl() {}
virtual std::string Start() { return {}; }
virtual std::string End() { return {}; }
- virtual std::string Text(const android::StringPiece& text) = 0;
- virtual std::string Placeholder(const android::StringPiece& text) = 0;
+ virtual std::string Text(android::StringPiece text) = 0;
+ virtual std::string Placeholder(android::StringPiece text) = 0;
};
class Pseudolocalizer {
@@ -47,7 +47,7 @@
void SetMethod(Method method);
std::string Start() { return impl_->Start(); }
std::string End() { return impl_->End(); }
- std::string Text(const android::StringPiece& text);
+ std::string Text(android::StringPiece text);
private:
std::unique_ptr<PseudoMethodImpl> impl_;
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index 6bba11e..1b03253 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -152,7 +152,7 @@
* success, or false if the either the placeholder is not found in the name, or the value is not
* present and the placeholder was.
*/
-bool ReplacePlaceholder(const StringPiece& placeholder, const std::optional<StringPiece>& value,
+bool ReplacePlaceholder(StringPiece placeholder, const std::optional<StringPiece>& value,
std::string* name, android::IDiagnostics* diag) {
size_t offset = name->find(placeholder.data());
bool found = (offset != std::string::npos);
@@ -338,17 +338,17 @@
return {config};
}
-const StringPiece& AbiToString(Abi abi) {
+StringPiece AbiToString(Abi abi) {
return kAbiToStringMap.at(static_cast<size_t>(abi));
}
/**
* Returns the common artifact base name from a template string.
*/
-std::optional<std::string> ToBaseName(std::string result, const StringPiece& apk_name,
+std::optional<std::string> ToBaseName(std::string result, StringPiece apk_name,
android::IDiagnostics* diag) {
const StringPiece ext = file::GetExtension(apk_name);
- size_t end_index = apk_name.to_string().rfind(ext.to_string());
+ size_t end_index = apk_name.rfind(ext);
const std::string base_name =
(end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : "";
@@ -371,17 +371,17 @@
// If no extension is specified, and the name template does not end in the current extension,
// add the existing extension.
if (!util::EndsWith(result, ext)) {
- result.append(ext.to_string());
+ result.append(ext);
}
}
return result;
}
-std::optional<std::string> ConfiguredArtifact::ToArtifactName(const StringPiece& format,
- const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::ToArtifactName(StringPiece format,
+ StringPiece apk_name,
android::IDiagnostics* diag) const {
- std::optional<std::string> base = ToBaseName(format.to_string(), apk_name, diag);
+ std::optional<std::string> base = ToBaseName(std::string(format), apk_name, diag);
if (!base) {
return {};
}
@@ -414,7 +414,7 @@
return result;
}
-std::optional<std::string> ConfiguredArtifact::Name(const StringPiece& apk_name,
+std::optional<std::string> ConfiguredArtifact::Name(StringPiece apk_name,
android::IDiagnostics* diag) const {
if (!name) {
return {};
@@ -439,7 +439,7 @@
}
std::optional<std::vector<OutputArtifact>> ConfigurationParser::Parse(
- const android::StringPiece& apk_path) {
+ android::StringPiece apk_path) {
std::optional<PostProcessingConfiguration> maybe_config =
ExtractConfiguration(contents_, config_path_, diag_);
if (!maybe_config) {
@@ -447,7 +447,7 @@
}
// Convert from a parsed configuration to a list of artifacts for processing.
- const std::string& apk_name = file::GetFilename(apk_path).to_string();
+ const std::string apk_name(file::GetFilename(apk_path));
std::vector<OutputArtifact> output_artifacts;
PostProcessingConfiguration& config = maybe_config.value();
@@ -519,7 +519,7 @@
for (auto& node : root_element->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- config->artifact_format = TrimWhitespace(t->text).to_string();
+ config->artifact_format.emplace(TrimWhitespace(t->text));
break;
}
}
@@ -561,7 +561,7 @@
for (auto& node : child->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+ auto abi = kStringToAbiMap.find(TrimWhitespace(t->text));
if (abi != kStringToAbiMap.end()) {
group.push_back(abi->second);
} else {
@@ -622,7 +622,7 @@
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
ConfigDescription config_descriptor;
- const android::StringPiece& text = TrimWhitespace(t->text);
+ android::StringPiece text = TrimWhitespace(t->text);
bool parsed = ConfigDescription::Parse(text, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -688,7 +688,7 @@
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
ConfigDescription config_descriptor;
- const android::StringPiece& text = TrimWhitespace(t->text);
+ android::StringPiece text = TrimWhitespace(t->text);
bool parsed = ConfigDescription::Parse(text, &config_descriptor);
if (parsed &&
(config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
@@ -806,7 +806,7 @@
for (auto& node : element->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- result.texture_paths.push_back(TrimWhitespace(t->text).to_string());
+ result.texture_paths.emplace_back(TrimWhitespace(t->text));
}
}
}
@@ -843,7 +843,7 @@
for (auto& node : child->children) {
xml::Text* t;
if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
- group.push_back(TrimWhitespace(t->text).to_string());
+ group.emplace_back(TrimWhitespace(t->text));
break;
}
}
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index 2c8221d..d66f4ab 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -43,7 +43,7 @@
};
/** Helper method to convert an ABI to a string representing the path within the APK. */
-const android::StringPiece& AbiToString(Abi abi);
+android::StringPiece AbiToString(Abi abi);
/**
* Represents an individual locale. When a locale is included, it must be
@@ -150,8 +150,7 @@
* Parses the configuration file and returns the results. If the configuration could not be parsed
* the result is empty and any errors will be displayed with the provided diagnostics context.
*/
- std::optional<std::vector<configuration::OutputArtifact>> Parse(
- const android::StringPiece& apk_path);
+ std::optional<std::vector<configuration::OutputArtifact>> Parse(android::StringPiece apk_path);
protected:
/**
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 3028c3f..198f730 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -138,13 +138,12 @@
std::optional<std::string> gl_texture_group;
/** Convert an artifact name template into a name string based on configuration contents. */
- std::optional<std::string> ToArtifactName(const android::StringPiece& format,
- const android::StringPiece& apk_name,
+ std::optional<std::string> ToArtifactName(android::StringPiece format,
+ android::StringPiece apk_name,
android::IDiagnostics* diag) const;
/** Convert an artifact name template into a name string based on configuration contents. */
- std::optional<std::string> Name(const android::StringPiece& apk_name,
- android::IDiagnostics* diag) const;
+ std::optional<std::string> Name(android::StringPiece apk_name, android::IDiagnostics* diag) const;
};
/** AAPT2 XML configuration file binary representation. */
diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp
index c4c002d..d60869a 100644
--- a/tools/aapt2/dump/DumpManifest.cpp
+++ b/tools/aapt2/dump/DumpManifest.cpp
@@ -1076,7 +1076,7 @@
/** Adds a feature to the feature group. */
void AddFeature(const std::string& name, bool required = true, int32_t version = -1) {
- features_.insert(std::make_pair(name, Feature{ required, version }));
+ features_.insert_or_assign(name, Feature{required, version});
if (required) {
if (name == "android.hardware.camera.autofocus" ||
name == "android.hardware.camera.flash") {
@@ -1348,6 +1348,11 @@
std::string impliedReason;
void Extract(xml::Element* element) override {
+ const auto parent_stack = extractor()->parent_stack();
+ if (!extractor()->options_.only_permissions &&
+ (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+ return;
+ }
name = GetAttributeStringDefault(FindAttribute(element, NAME_ATTR), "");
std::string feature =
GetAttributeStringDefault(FindAttribute(element, REQUIRED_FEATURE_ATTR), "");
@@ -1472,6 +1477,11 @@
const int32_t* maxSdkVersion = nullptr;
void Extract(xml::Element* element) override {
+ const auto parent_stack = extractor()->parent_stack();
+ if (!extractor()->options_.only_permissions &&
+ (parent_stack.size() != 1 || !ElementCast<Manifest>(parent_stack[0]))) {
+ return;
+ }
name = GetAttributeString(FindAttribute(element, NAME_ATTR));
maxSdkVersion = GetAttributeInteger(FindAttribute(element, MAX_SDK_VERSION_ATTR));
diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp
index 9ace82a..908b171 100644
--- a/tools/aapt2/filter/AbiFilter.cpp
+++ b/tools/aapt2/filter/AbiFilter.cpp
@@ -23,15 +23,15 @@
namespace aapt {
std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) {
- std::unordered_set<std::string> abi_set;
+ std::unordered_set<std::string_view> abi_set;
for (auto& abi : abi_list) {
- abi_set.insert(configuration::AbiToString(abi).to_string());
+ abi_set.insert(configuration::AbiToString(abi));
}
// Make unique by hand as the constructor is private.
- return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set));
+ return std::unique_ptr<AbiFilter>(new AbiFilter(std::move(abi_set)));
}
-bool AbiFilter::Keep(const std::string& path) {
+bool AbiFilter::Keep(std::string_view path) {
// We only care about libraries.
if (!util::StartsWith(path, kLibPrefix)) {
return true;
@@ -44,7 +44,7 @@
}
// Strip the lib/ prefix.
- const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
+ const auto path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen);
return (abis_.find(path_abi) != abis_.end());
}
diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h
index 2832711..7380f3f 100644
--- a/tools/aapt2/filter/AbiFilter.h
+++ b/tools/aapt2/filter/AbiFilter.h
@@ -18,7 +18,7 @@
#define AAPT2_ABISPLITTER_H
#include <memory>
-#include <string>
+#include <string_view>
#include <unordered_set>
#include <vector>
@@ -39,16 +39,16 @@
static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list);
/** Returns true if the path is for a native library in the list of desired ABIs. */
- bool Keep(const std::string& path) override;
+ bool Keep(std::string_view path) override;
private:
- explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) {
+ explicit AbiFilter(std::unordered_set<std::string_view> abis) : abis_(std::move(abis)) {
}
/** The path prefix to where all native libs end up inside an APK file. */
static constexpr const char* kLibPrefix = "lib/";
static constexpr size_t kLibPrefixLen = 4;
- const std::unordered_set<std::string> abis_;
+ const std::unordered_set<std::string_view> abis_;
};
} // namespace aapt
diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h
index f932f9c..baf4791 100644
--- a/tools/aapt2/filter/Filter.h
+++ b/tools/aapt2/filter/Filter.h
@@ -18,6 +18,7 @@
#define AAPT2_FILTER_H
#include <string>
+#include <string_view>
#include <vector>
#include "util/Util.h"
@@ -30,7 +31,7 @@
virtual ~IPathFilter() = default;
/** Returns true if the path should be kept. */
- virtual bool Keep(const std::string& path) = 0;
+ virtual bool Keep(std::string_view path) = 0;
};
/**
@@ -42,7 +43,7 @@
}
/** Returns true if the provided path matches the prefix. */
- bool Keep(const std::string& path) override {
+ bool Keep(std::string_view path) override {
return util::StartsWith(path, prefix_);
}
@@ -59,7 +60,7 @@
}
/** Returns true if all filters keep the path. */
- bool Keep(const std::string& path) override {
+ bool Keep(std::string_view path) override {
for (auto& filter : filters_) {
if (!filter->Keep(path)) {
return false;
diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp
index 80c1618..e9a93d8 100644
--- a/tools/aapt2/format/Archive.cpp
+++ b/tools/aapt2/format/Archive.cpp
@@ -40,8 +40,8 @@
public:
DirectoryWriter() = default;
- bool Open(const StringPiece& out_dir) {
- dir_ = out_dir.to_string();
+ bool Open(StringPiece out_dir) {
+ dir_ = std::string(out_dir);
file::FileType type = file::GetFileType(dir_);
if (type == file::FileType::kNonExistant) {
error_ = "directory does not exist";
@@ -53,14 +53,14 @@
return true;
}
- bool StartEntry(const StringPiece& path, uint32_t flags) override {
+ bool StartEntry(StringPiece path, uint32_t flags) override {
if (file_) {
return false;
}
std::string full_path = dir_;
file::AppendPath(&full_path, path);
- file::mkdirs(file::GetStem(full_path).to_string());
+ file::mkdirs(std::string(file::GetStem(full_path)));
file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose};
if (!file_) {
@@ -91,7 +91,7 @@
return true;
}
- bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
if (!StartEntry(path, flags)) {
return false;
}
@@ -132,8 +132,8 @@
public:
ZipFileWriter() = default;
- bool Open(const StringPiece& path) {
- file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose};
+ bool Open(StringPiece path) {
+ file_ = {::android::base::utf8::fopen(path.data(), "w+b"), fclose};
if (!file_) {
error_ = SystemErrorCodeToString(errno);
return false;
@@ -142,7 +142,7 @@
return true;
}
- bool StartEntry(const StringPiece& path, uint32_t flags) override {
+ bool StartEntry(StringPiece path, uint32_t flags) override {
if (!writer_) {
return false;
}
@@ -182,7 +182,7 @@
return true;
}
- bool WriteFile(const StringPiece& path, uint32_t flags, io::InputStream* in) override {
+ bool WriteFile(StringPiece path, uint32_t flags, io::InputStream* in) override {
while (true) {
if (!StartEntry(path, flags)) {
return false;
@@ -257,7 +257,7 @@
} // namespace
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
- const StringPiece& path) {
+ StringPiece path) {
std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
if (!writer->Open(path)) {
diag->Error(android::DiagMessage(path) << writer->GetError());
@@ -267,7 +267,7 @@
}
std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
- const StringPiece& path) {
+ StringPiece path) {
std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
if (!writer->Open(path)) {
diag->Error(android::DiagMessage(path) << writer->GetError());
diff --git a/tools/aapt2/format/Archive.h b/tools/aapt2/format/Archive.h
index 55b0b2f..6cde753 100644
--- a/tools/aapt2/format/Archive.h
+++ b/tools/aapt2/format/Archive.h
@@ -46,12 +46,12 @@
public:
virtual ~IArchiveWriter() = default;
- virtual bool WriteFile(const android::StringPiece& path, uint32_t flags, io::InputStream* in) = 0;
+ virtual bool WriteFile(android::StringPiece path, uint32_t flags, io::InputStream* in) = 0;
// Starts a new entry and allows caller to write bytes to it sequentially.
// Only use StartEntry if code you do not control needs to write to a CopyingOutputStream.
// Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
- virtual bool StartEntry(const android::StringPiece& path, uint32_t flags) = 0;
+ virtual bool StartEntry(android::StringPiece path, uint32_t flags) = 0;
// Called to finish writing an entry previously started by StartEntry.
// Prefer WriteFile instead of manually calling StartEntry/FinishEntry.
@@ -70,10 +70,10 @@
};
std::unique_ptr<IArchiveWriter> CreateDirectoryArchiveWriter(android::IDiagnostics* diag,
- const android::StringPiece& path);
+ android::StringPiece path);
std::unique_ptr<IArchiveWriter> CreateZipFileArchiveWriter(android::IDiagnostics* diag,
- const android::StringPiece& path);
+ android::StringPiece path);
} // namespace aapt
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index ceed374..3c44da7 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -50,7 +50,7 @@
}
std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) {
- file::mkdirs(file::GetStem(output_path).to_string());
+ file::mkdirs(std::string(file::GetStem(output_path)));
std::remove(output_path.c_str());
StdErrDiagnostics diag;
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 8291862..75dcba5 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -373,7 +373,7 @@
std::optional<ResourceNamedTypeRef> parsed_type = ParseResourceNamedType(type_str);
if (!parsed_type) {
diag_->Warn(android::DiagMessage(source_)
- << "invalid type name '" << type_str << "' for type with ID " << type->id);
+ << "invalid type name '" << type_str << "' for type with ID " << int(type->id));
return true;
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f192234..8c594ba 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -32,6 +32,7 @@
#include "format/binary/ChunkWriter.h"
#include "format/binary/ResEntryWriter.h"
#include "format/binary/ResourceTypeExtensions.h"
+#include "optimize/Obfuscator.h"
#include "trace/TraceBuffer.h"
using namespace android;
@@ -466,9 +467,6 @@
// table.
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
- // hardcoded string uses characters which make it an invalid resource name
- const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
-
for (const ResourceTableEntryView& entry : type.entries) {
if (entry.staged_id) {
aliases_.insert(std::make_pair(
@@ -477,30 +475,31 @@
}
uint32_t local_key_index;
- ResourceName resource_name({}, type.named_type, entry.name);
- if (!collapse_key_stringpool_ ||
- name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
- local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
- } else {
- // resource isn't exempt from collapse, add it as obfuscated value
- if (entry.overlayable_item) {
+ auto onObfuscate = [this, &local_key_index, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Keep_ExemptionList) {
+ local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
+ } else if (obfuscatedResult == Obfuscator::Result::Keep_Overlayable) {
// if the resource name of the specific entry is obfuscated and this
// entry is in the overlayable list, the overlay can't work on this
// overlayable at runtime because the name has been obfuscated in
// resources.arsc during flatten operation.
const OverlayableItem& item = entry.overlayable_item.value();
context_->GetDiagnostics()->Warn(android::DiagMessage(item.overlayable->source)
- << "The resource name of overlayable entry "
- << resource_name.to_string() << "'"
- << " shouldn't be obfuscated in resources.arsc");
+ << "The resource name of overlayable entry '"
+ << resource_name.to_string()
+ << "' shouldn't be obfuscated in resources.arsc");
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
- // TODO(b/228192695): output the entry.name and Resource id to make
- // de-obfuscated possible.
- local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index();
+ local_key_index =
+ (uint32_t)key_pool_.MakeRef(Obfuscator::kObfuscatedResourceName).index();
}
- }
+ };
+
+ Obfuscator::ObfuscateResourceName(collapse_key_stringpool_, name_collapse_exemptions_,
+ type.named_type, entry, onObfuscate);
+
// Group values by configuration.
for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 35254ba..60605d2 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -14,8 +14,13 @@
* limitations under the License.
*/
-#ifndef AAPT_FORMAT_BINARY_TABLEFLATTENER_H
-#define AAPT_FORMAT_BINARY_TABLEFLATTENER_H
+#ifndef TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+#define TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <unordered_map>
#include "Resource.h"
#include "ResourceTable.h"
@@ -71,6 +76,9 @@
//
// This applies only to simple entries (entry->flags & ResTable_entry::FLAG_COMPLEX == 0).
bool deduplicate_entry_values = false;
+
+ // Map from original resource ids to obfuscated names.
+ std::unordered_map<uint32_t, std::string> id_resource_map;
};
class TableFlattener : public IResourceTableConsumer {
@@ -82,12 +90,12 @@
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- DISALLOW_COPY_AND_ASSIGN(TableFlattener);
-
TableFlattenerOptions options_;
android::BigBuffer* buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(TableFlattener);
};
} // namespace aapt
-#endif /* AAPT_FORMAT_BINARY_TABLEFLATTENER_H */
+#endif // TOOLS_AAPT2_FORMAT_BINARY_TABLEFLATTENER_H_
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index d08b4a3..0f11685 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -84,7 +84,7 @@
return ::testing::AssertionSuccess();
}
- ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
+ ::testing::AssertionResult Exists(ResTable* table, StringPiece expected_name,
const ResourceId& expected_id,
const ConfigDescription& expected_config,
const uint8_t expected_data_type, const uint32_t expected_data,
diff --git a/tools/aapt2/format/binary/XmlFlattener.cpp b/tools/aapt2/format/binary/XmlFlattener.cpp
index 983e646..05f9751 100644
--- a/tools/aapt2/format/binary/XmlFlattener.cpp
+++ b/tools/aapt2/format/binary/XmlFlattener.cpp
@@ -79,7 +79,7 @@
}
void Visit(const xml::Text* node) override {
- std::string text = util::TrimWhitespace(node->text).to_string();
+ std::string text(util::TrimWhitespace(node->text));
// Skip whitespace only text nodes.
if (text.empty()) {
@@ -88,10 +88,10 @@
// Compact leading and trailing whitespace into a single space
if (isspace(node->text[0])) {
- text = ' ' + text;
+ text.insert(text.begin(), ' ');
}
- if (isspace(node->text[node->text.length() - 1])) {
- text = text + ' ';
+ if (isspace(node->text.back())) {
+ text += ' ';
}
ChunkWriter writer(buffer_);
@@ -165,7 +165,7 @@
// We are adding strings to a StringPool whose strings will be sorted and merged with other
// string pools. That means we can't encode the ID of a string directly. Instead, we defer the
// writing of the ID here, until after the StringPool is merged and sorted.
- void AddString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest,
+ void AddString(StringPiece str, uint32_t priority, android::ResStringPool_ref* dest,
bool treat_empty_string_as_null = false) {
if (str.empty() && treat_empty_string_as_null) {
// Some parts of the runtime treat null differently than empty string.
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index a6d58fd..0e40124 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -18,6 +18,7 @@
#include "ValueVisitor.h"
#include "androidfw/BigBuffer.h"
+#include "optimize/Obfuscator.h"
using android::ConfigDescription;
@@ -366,21 +367,21 @@
}
pb_type->set_name(type.named_type.to_string());
- // hardcoded string uses characters which make it an invalid resource name
- static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
for (const auto& entry : type.entries) {
pb::Entry* pb_entry = pb_type->add_entry();
if (entry.id) {
pb_entry->mutable_entry_id()->set_id(entry.id.value());
}
- ResourceName resource_name({}, type.named_type, entry.name);
- if (options.collapse_key_stringpool &&
- options.name_collapse_exemptions.find(resource_name) ==
- options.name_collapse_exemptions.end()) {
- pb_entry->set_name(obfuscated_resource_name);
- } else {
- pb_entry->set_name(entry.name);
- }
+ auto onObfuscate = [pb_entry, &entry](Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ pb_entry->set_name(obfuscatedResult == Obfuscator::Result::Obfuscated
+ ? Obfuscator::kObfuscatedResourceName
+ : entry.name);
+ };
+
+ Obfuscator::ObfuscateResourceName(options.collapse_key_stringpool,
+ options.name_collapse_exemptions, type.named_type, entry,
+ onObfuscate);
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 5adc5e6..ecfdba8 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -35,7 +35,7 @@
class MockFileCollection : public io::IFileCollection {
public:
- MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+ MOCK_METHOD1(FindFile, io::IFile*(StringPiece path));
MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
MOCK_METHOD0(GetDirSeparator, char());
};
@@ -491,7 +491,7 @@
EXPECT_THAT(bp->value.data, Eq(ResourceUtils::MakeEmpty()->value.data));
}
-static void ExpectConfigSerializes(const StringPiece& config_str) {
+static void ExpectConfigSerializes(StringPiece config_str) {
const ConfigDescription expected_config = test::ParseConfigOrDie(config_str);
pb::Configuration pb_config;
SerializeConfig(expected_config, &pb_config);
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 422658a..08d497d 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -101,7 +101,7 @@
public:
virtual ~IFileCollection() = default;
- virtual IFile* FindFile(const android::StringPiece& path) = 0;
+ virtual IFile* FindFile(android::StringPiece path) = 0;
virtual std::unique_ptr<IFileCollectionIterator> Iterator() = 0;
virtual char GetDirSeparator() = 0;
};
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 3f071af..a64982a 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -67,8 +67,8 @@
return result;
}
-std::unique_ptr<FileCollection> FileCollection::Create(const android::StringPiece& root,
- std::string* outError) {
+std::unique_ptr<FileCollection> FileCollection::Create(android::StringPiece root,
+ std::string* outError) {
std::unique_ptr<FileCollection> collection =
std::unique_ptr<FileCollection>(new FileCollection());
@@ -80,7 +80,7 @@
std::vector<std::string> sorted_files;
while (struct dirent *entry = readdir(d.get())) {
- std::string prefix_path = root.to_string();
+ std::string prefix_path(root);
file::AppendPath(&prefix_path, entry->d_name);
// The directory to iterate over looking for files
@@ -117,12 +117,19 @@
return collection;
}
-IFile* FileCollection::InsertFile(const StringPiece& path) {
- return (files_[path.to_string()] = util::make_unique<RegularFile>(android::Source(path))).get();
+IFile* FileCollection::InsertFile(StringPiece path) {
+ auto file = util::make_unique<RegularFile>(android::Source(path));
+ auto it = files_.lower_bound(path);
+ if (it != files_.end() && it->first == path) {
+ it->second = std::move(file);
+ } else {
+ it = files_.emplace_hint(it, path, std::move(file));
+ }
+ return it->second.get();
}
-IFile* FileCollection::FindFile(const StringPiece& path) {
- auto iter = files_.find(path.to_string());
+IFile* FileCollection::FindFile(StringPiece path) {
+ auto iter = files_.find(path);
if (iter != files_.end()) {
return iter->second.get();
}
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index bc03b9b..0e798fc 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -60,12 +60,11 @@
FileCollection() = default;
/** Creates a file collection containing all files contained in the specified root directory. */
- static std::unique_ptr<FileCollection> Create(const android::StringPiece& path,
- std::string* outError);
+ static std::unique_ptr<FileCollection> Create(android::StringPiece path, std::string* outError);
// Adds a file located at path. Returns the IFile representation of that file.
- IFile* InsertFile(const android::StringPiece& path);
- IFile* FindFile(const android::StringPiece& path) override;
+ IFile* InsertFile(android::StringPiece path);
+ IFile* FindFile(android::StringPiece path) override;
std::unique_ptr<IFileCollectionIterator> Iterator() override;
char GetDirSeparator() override;
@@ -74,7 +73,7 @@
friend class FileCollectionIterator;
- std::map<std::string, std::unique_ptr<IFile>> files_;
+ std::map<std::string, std::unique_ptr<IFile>, std::less<>> files_;
};
} // namespace io
diff --git a/tools/aapt2/io/StringStream.cpp b/tools/aapt2/io/StringStream.cpp
index 4ca04a8..9c49788 100644
--- a/tools/aapt2/io/StringStream.cpp
+++ b/tools/aapt2/io/StringStream.cpp
@@ -21,7 +21,7 @@
namespace aapt {
namespace io {
-StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) {
+StringInputStream::StringInputStream(StringPiece str) : str_(str), offset_(0u) {
}
bool StringInputStream::Next(const void** data, size_t* size) {
diff --git a/tools/aapt2/io/StringStream.h b/tools/aapt2/io/StringStream.h
index f29890a..f7bdecca 100644
--- a/tools/aapt2/io/StringStream.h
+++ b/tools/aapt2/io/StringStream.h
@@ -29,7 +29,7 @@
class StringInputStream : public KnownSizeInputStream {
public:
- explicit StringInputStream(const android::StringPiece& str);
+ explicit StringInputStream(android::StringPiece str);
bool Next(const void** data, size_t* size) override;
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index afe54d4..79d8d52 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -26,7 +26,7 @@
namespace aapt {
namespace io {
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
uint32_t compression_flags, IArchiveWriter* writer) {
TRACE_CALL();
if (context->IsVerbose()) {
@@ -43,7 +43,7 @@
return true;
}
-bool CopyFileToArchive(IAaptContext* context, io::IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, io::IFile* file, std::string_view out_path,
uint32_t compression_flags, IArchiveWriter* writer) {
TRACE_CALL();
std::unique_ptr<io::IData> data = file->OpenAsData();
@@ -56,13 +56,13 @@
}
bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file,
- const std::string& out_path, IArchiveWriter* writer) {
+ std::string_view out_path, IArchiveWriter* writer) {
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
return CopyFileToArchive(context, file, out_path, compression_flags, writer);
}
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
- const std::string& out_path, uint32_t compression_flags,
+ std::string_view out_path, uint32_t compression_flags,
IArchiveWriter* writer) {
TRACE_CALL();
if (context->IsVerbose()) {
@@ -110,7 +110,7 @@
return !in->HadError();
}
-bool Copy(OutputStream* out, const StringPiece& in) {
+bool Copy(OutputStream* out, StringPiece in) {
const char* in_buffer = in.data();
size_t in_len = in.size();
while (in_len != 0) {
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index 1b48a28..685f522 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -17,12 +17,11 @@
#ifndef AAPT_IO_UTIL_H
#define AAPT_IO_UTIL_H
-#include <string>
-
-#include "google/protobuf/message.h"
-#include "google/protobuf/io/coded_stream.h"
+#include <string_view>
#include "format/Archive.h"
+#include "google/protobuf/io/coded_stream.h"
+#include "google/protobuf/message.h"
#include "io/File.h"
#include "io/Io.h"
#include "process/IResourceTableConsumer.h"
@@ -30,23 +29,23 @@
namespace aapt {
namespace io {
-bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, const std::string& out_path,
+bool CopyInputStreamToArchive(IAaptContext* context, InputStream* in, std::string_view out_path,
uint32_t compression_flags, IArchiveWriter* writer);
-bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
+bool CopyFileToArchive(IAaptContext* context, IFile* file, std::string_view out_path,
uint32_t compression_flags, IArchiveWriter* writer);
bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
- const std::string& out_path, IArchiveWriter* writer);
+ std::string_view out_path, IArchiveWriter* writer);
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg,
- const std::string& out_path, uint32_t compression_flags,
+ std::string_view out_path, uint32_t compression_flags,
IArchiveWriter* writer);
// Copies the data from in to out. Returns false if there was an error.
// If there was an error, check the individual streams' HadError/GetError methods.
bool Copy(OutputStream* out, InputStream* in);
-bool Copy(OutputStream* out, const ::android::StringPiece& in);
+bool Copy(OutputStream* out, android::StringPiece in);
bool Copy(::google::protobuf::io::ZeroCopyOutputStream* out, InputStream* in);
class OutputStreamAdaptor : public io::OutputStream {
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 400269c..4a5385d 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -91,8 +91,8 @@
ZipFileCollection::ZipFileCollection() : handle_(nullptr) {}
-std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(
- const StringPiece& path, std::string* out_error) {
+std::unique_ptr<ZipFileCollection> ZipFileCollection::Create(StringPiece path,
+ std::string* out_error) {
TRACE_CALL();
constexpr static const int32_t kEmptyArchive = -6;
@@ -130,8 +130,8 @@
continue;
}
- std::unique_ptr<IFile> file = util::make_unique<ZipFile>(
- collection->handle_, zip_data, android::Source(zip_entry_path, path.to_string()));
+ std::unique_ptr<IFile> file = util::make_unique<ZipFile>(collection->handle_, zip_data,
+ android::Source(zip_entry_path, path));
collection->files_by_name_[zip_entry_path] = file.get();
collection->files_.push_back(std::move(file));
}
@@ -144,8 +144,8 @@
return collection;
}
-IFile* ZipFileCollection::FindFile(const StringPiece& path) {
- auto iter = files_by_name_.find(path.to_string());
+IFile* ZipFileCollection::FindFile(StringPiece path) {
+ auto iter = files_by_name_.find(path);
if (iter != files_by_name_.end()) {
return iter->second;
}
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index 78c9c21..c263aa4 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -61,10 +61,10 @@
// An IFileCollection that represents a ZIP archive and the entries within it.
class ZipFileCollection : public IFileCollection {
public:
- static std::unique_ptr<ZipFileCollection> Create(const android::StringPiece& path,
+ static std::unique_ptr<ZipFileCollection> Create(android::StringPiece path,
std::string* outError);
- io::IFile* FindFile(const android::StringPiece& path) override;
+ io::IFile* FindFile(android::StringPiece path) override;
std::unique_ptr<IFileCollectionIterator> Iterator() override;
char GetDirSeparator() override;
@@ -76,7 +76,7 @@
ZipArchiveHandle handle_;
std::vector<std::unique_ptr<IFile>> files_;
- std::map<std::string, IFile*> files_by_name_;
+ std::map<std::string, IFile*, std::less<>> files_by_name_;
};
} // namespace io
diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp
index 482d91a..87da09a 100644
--- a/tools/aapt2/java/AnnotationProcessor.cpp
+++ b/tools/aapt2/java/AnnotationProcessor.cpp
@@ -30,7 +30,7 @@
namespace aapt {
-StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) {
+StringPiece AnnotationProcessor::ExtractFirstSentence(StringPiece comment) {
Utf8Iterator iter(comment);
while (iter.HasNext()) {
const char32_t codepoint = iter.Next();
@@ -62,7 +62,7 @@
}};
void AnnotationProcessor::AppendCommentLine(std::string comment) {
- static const std::string sDeprecated = "@deprecated";
+ static constexpr std::string_view sDeprecated = "@deprecated";
// Treat deprecated specially, since we don't remove it from the source comment.
if (comment.find(sDeprecated) != std::string::npos) {
@@ -74,7 +74,7 @@
if (idx != std::string::npos) {
// Captures all parameters associated with the specified annotation rule
// by matching the first pair of parantheses after the rule.
- std::regex re(rule.doc_str.to_string() + "\\s*\\((.+)\\)");
+ std::regex re(std::string(rule.doc_str) += "\\s*\\((.+)\\)");
std::smatch match_result;
const bool is_match = std::regex_search(comment, match_result, re);
// We currently only capture and preserve parameters for SystemApi.
@@ -97,7 +97,7 @@
// If there was trimming to do, copy the string.
if (trimmed.size() != comment.size()) {
- comment = trimmed.to_string();
+ comment = std::string(trimmed);
}
if (!has_comments_) {
@@ -107,12 +107,12 @@
comment_ << "\n * " << std::move(comment);
}
-void AnnotationProcessor::AppendComment(const StringPiece& comment) {
+void AnnotationProcessor::AppendComment(StringPiece comment) {
// We need to process line by line to clean-up whitespace and append prefixes.
for (StringPiece line : util::Tokenize(comment, '\n')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- AppendCommentLine(line.to_string());
+ AppendCommentLine(std::string(line));
}
}
}
@@ -126,7 +126,7 @@
void AnnotationProcessor::Print(Printer* printer, bool strip_api_annotations) const {
if (has_comments_) {
std::string result = comment_.str();
- for (const StringPiece& line : util::Tokenize(result, '\n')) {
+ for (StringPiece line : util::Tokenize(result, '\n')) {
printer->Println(line);
}
printer->Println(" */");
diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h
index f217afb..db3437e 100644
--- a/tools/aapt2/java/AnnotationProcessor.h
+++ b/tools/aapt2/java/AnnotationProcessor.h
@@ -56,11 +56,11 @@
// Extracts the first sentence of a comment. The algorithm selects the substring starting from
// the beginning of the string, and ending at the first '.' character that is followed by a
// whitespace character. If these requirements are not met, the whole string is returned.
- static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment);
+ static android::StringPiece ExtractFirstSentence(android::StringPiece comment);
// Adds more comments. Resources can have value definitions for various configurations, and
// each of the definitions may have comments that need to be processed.
- void AppendComment(const android::StringPiece& comment);
+ void AppendComment(android::StringPiece comment);
void AppendNewLine();
diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp
index 3163497..98f3bd2 100644
--- a/tools/aapt2/java/ClassDefinition.cpp
+++ b/tools/aapt2/java/ClassDefinition.cpp
@@ -27,8 +27,8 @@
processor_.Print(printer, strip_api_annotations);
}
-void MethodDefinition::AppendStatement(const StringPiece& statement) {
- statements_.push_back(statement.to_string());
+void MethodDefinition::AppendStatement(StringPiece statement) {
+ statements_.emplace_back(statement);
}
void MethodDefinition::Print(bool final, Printer* printer, bool) const {
@@ -110,8 +110,8 @@
" * should not be modified by hand.\n"
" */\n\n";
-void ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package,
- bool final, bool strip_api_annotations, io::OutputStream* out) {
+void ClassDefinition::WriteJavaFile(const ClassDefinition* def, StringPiece package, bool final,
+ bool strip_api_annotations, io::OutputStream* out) {
Printer printer(out);
printer.Print(sWarningHeader).Print("package ").Print(package).Println(";");
printer.Println();
diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h
index 2acdadb..63c9982 100644
--- a/tools/aapt2/java/ClassDefinition.h
+++ b/tools/aapt2/java/ClassDefinition.h
@@ -59,8 +59,8 @@
template <typename T>
class PrimitiveMember : public ClassMember {
public:
- PrimitiveMember(const android::StringPiece& name, const T& val, bool staged_api = false)
- : name_(name.to_string()), val_(val), staged_api_(staged_api) {
+ PrimitiveMember(android::StringPiece name, const T& val, bool staged_api = false)
+ : name_(name), val_(val), staged_api_(staged_api) {
}
bool empty() const override {
@@ -104,8 +104,8 @@
template <>
class PrimitiveMember<std::string> : public ClassMember {
public:
- PrimitiveMember(const android::StringPiece& name, const std::string& val, bool staged_api = false)
- : name_(name.to_string()), val_(val) {
+ PrimitiveMember(android::StringPiece name, const std::string& val, bool staged_api = false)
+ : name_(name), val_(val) {
}
bool empty() const override {
@@ -141,7 +141,8 @@
template <typename T, typename StringConverter>
class PrimitiveArrayMember : public ClassMember {
public:
- explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
+ explicit PrimitiveArrayMember(android::StringPiece name) : name_(name) {
+ }
void AddElement(const T& val) {
elements_.emplace_back(val);
@@ -209,12 +210,12 @@
class MethodDefinition : public ClassMember {
public:
// Expected method signature example: 'public static void onResourcesLoaded(int p)'.
- explicit MethodDefinition(const android::StringPiece& signature)
- : signature_(signature.to_string()) {}
+ explicit MethodDefinition(android::StringPiece signature) : signature_(signature) {
+ }
// Appends a single statement to the method. It should include no newlines or else
// formatting may be broken.
- void AppendStatement(const android::StringPiece& statement);
+ void AppendStatement(android::StringPiece statement);
// Not quite the same as a name, but good enough.
const std::string& GetName() const override {
@@ -239,11 +240,12 @@
class ClassDefinition : public ClassMember {
public:
- static void WriteJavaFile(const ClassDefinition* def, const android::StringPiece& package,
- bool final, bool strip_api_annotations, io::OutputStream* out);
+ static void WriteJavaFile(const ClassDefinition* def, android::StringPiece package, bool final,
+ bool strip_api_annotations, io::OutputStream* out);
- ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
- : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
+ ClassDefinition(android::StringPiece name, ClassQualifier qualifier, bool createIfEmpty)
+ : name_(name), qualifier_(qualifier), create_if_empty_(createIfEmpty) {
+ }
enum class Result {
kAdded,
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index a25ca22..7665d0e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -57,14 +57,14 @@
"transient", "try", "void", "volatile", "while",
"true", "false", "null"};
-static bool IsValidSymbol(const StringPiece& symbol) {
+static bool IsValidSymbol(StringPiece symbol) {
return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
}
// Java symbols can not contain . or -, but those are valid in a resource name.
// Replace those with '_'.
-std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
- std::string output = symbol.to_string();
+std::string JavaClassGenerator::TransformToFieldName(StringPiece symbol) {
+ std::string output(symbol);
for (char& c : output) {
if (c == '.' || c == '-') {
c = '_';
@@ -84,7 +84,7 @@
// Foo_bar
static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
const std::string& styleable_class_name,
- const StringPiece& package_name_to_generate) {
+ StringPiece package_name_to_generate) {
std::string output = styleable_class_name;
// We may reference IDs from other packages, so prefix the entry name with
@@ -226,16 +226,15 @@
static FieldReference GetRFieldReference(const ResourceName& name,
StringPiece fallback_package_name) {
- const std::string package_name =
- name.package.empty() ? fallback_package_name.to_string() : name.package;
+ const std::string_view package_name = name.package.empty() ? fallback_package_name : name.package;
const std::string entry = JavaClassGenerator::TransformToFieldName(name.entry);
- return FieldReference(StringPrintf("%s.R.%s.%s", package_name.c_str(),
- name.type.to_string().data(), entry.c_str()));
+ return FieldReference(
+ StringPrintf("%s.R.%s.%s", package_name.data(), name.type.to_string().data(), entry.c_str()));
}
bool JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
const Styleable& styleable,
- const StringPiece& package_name_to_generate,
+ StringPiece package_name_to_generate,
ClassDefinition* out_class_def,
MethodDefinition* out_rewrite_method,
Printer* r_txt_printer) {
@@ -314,7 +313,8 @@
return true;
}
const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment();
- return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide");
+ return attr_comment_line.find("@removed") != std::string::npos ||
+ attr_comment_line.find("@hide") != std::string::npos;
});
documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end());
@@ -397,7 +397,7 @@
comment = styleable_attr.symbol.value().attribute->GetComment();
}
- if (comment.contains("@removed")) {
+ if (comment.find("@removed") != std::string::npos) {
// Removed attributes are public but hidden from the documentation, so
// don't emit them as part of the class documentation.
continue;
@@ -497,7 +497,7 @@
}
if (out_rewrite_method != nullptr) {
- const std::string type_str = name.type.to_string();
+ const auto type_str = name.type.to_string();
out_rewrite_method->AppendStatement(
StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(),
field_name.data(), type_str.data(), field_name.data()));
@@ -505,8 +505,7 @@
}
std::optional<std::string> JavaClassGenerator::UnmangleResource(
- const StringPiece& package_name, const StringPiece& package_name_to_generate,
- const ResourceEntry& entry) {
+ StringPiece package_name, StringPiece package_name_to_generate, const ResourceEntry& entry) {
if (SkipSymbol(entry.visibility.level)) {
return {};
}
@@ -528,7 +527,7 @@
return {std::move(unmangled_name)};
}
-bool JavaClassGenerator::ProcessType(const StringPiece& package_name_to_generate,
+bool JavaClassGenerator::ProcessType(StringPiece package_name_to_generate,
const ResourceTablePackage& package,
const ResourceTableType& type,
ClassDefinition* out_type_class_def,
@@ -577,7 +576,7 @@
return true;
}
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate, OutputStream* out,
OutputStream* out_r_txt) {
return Generate(package_name_to_generate, package_name_to_generate, out, out_r_txt);
}
@@ -591,8 +590,8 @@
}
}
-bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate,
- const StringPiece& out_package_name, OutputStream* out,
+bool JavaClassGenerator::Generate(StringPiece package_name_to_generate,
+ StringPiece out_package_name, OutputStream* out,
OutputStream* out_r_txt) {
ClassDefinition r_class("R", ClassQualifier::kNone, true);
std::unique_ptr<MethodDefinition> rewrite_method;
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index b45a2f1..234df04 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -70,16 +70,16 @@
// All symbols technically belong to a single package, but linked libraries will
// have their names mangled, denoting that they came from a different package.
// We need to generate these symbols in a separate file. Returns true on success.
- bool Generate(const android::StringPiece& package_name_to_generate, io::OutputStream* out,
+ bool Generate(android::StringPiece package_name_to_generate, io::OutputStream* out,
io::OutputStream* out_r_txt = nullptr);
- bool Generate(const android::StringPiece& package_name_to_generate,
- const android::StringPiece& output_package_name, io::OutputStream* out,
+ bool Generate(android::StringPiece package_name_to_generate,
+ android::StringPiece output_package_name, io::OutputStream* out,
io::OutputStream* out_r_txt = nullptr);
const std::string& GetError() const;
- static std::string TransformToFieldName(const android::StringPiece& symbol);
+ static std::string TransformToFieldName(android::StringPiece symbol);
private:
bool SkipSymbol(Visibility::Level state);
@@ -87,11 +87,11 @@
// Returns the unmangled resource entry name if the unmangled package is the same as
// package_name_to_generate. Returns nothing if the resource should be skipped.
- std::optional<std::string> UnmangleResource(const android::StringPiece& package_name,
- const android::StringPiece& package_name_to_generate,
+ std::optional<std::string> UnmangleResource(android::StringPiece package_name,
+ android::StringPiece package_name_to_generate,
const ResourceEntry& entry);
- bool ProcessType(const android::StringPiece& package_name_to_generate,
+ bool ProcessType(android::StringPiece package_name_to_generate,
const ResourceTablePackage& package, const ResourceTableType& type,
ClassDefinition* out_type_class_def, MethodDefinition* out_rewrite_method_def,
text::Printer* r_txt_printer);
@@ -106,8 +106,7 @@
// its package ID if `out_rewrite_method` is not nullptr.
// `package_name_to_generate` is the package
bool ProcessStyleable(const ResourceNameRef& name, const ResourceId& id,
- const Styleable& styleable,
- const android::StringPiece& package_name_to_generate,
+ const Styleable& styleable, android::StringPiece package_name_to_generate,
ClassDefinition* out_class_def, MethodDefinition* out_rewrite_method,
text::Printer* r_txt_printer);
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index d0850b8..56d9075 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -646,8 +646,8 @@
return true;
}
-static void FullyQualifyClassName(const StringPiece& package, const StringPiece& attr_ns,
- const StringPiece& attr_name, xml::Element* el) {
+static void FullyQualifyClassName(StringPiece package, StringPiece attr_ns, StringPiece attr_name,
+ xml::Element* el) {
xml::Attribute* attr = el->FindAttribute(attr_ns, attr_name);
if (attr != nullptr) {
if (std::optional<std::string> new_value =
@@ -657,7 +657,7 @@
}
}
-static bool RenameManifestPackage(const StringPiece& package_override, xml::Element* manifest_el) {
+static bool RenameManifestPackage(StringPiece package_override, xml::Element* manifest_el) {
xml::Attribute* attr = manifest_el->FindAttribute({}, "package");
// We've already verified that the manifest element is present, with a package
@@ -665,7 +665,7 @@
CHECK(attr != nullptr);
std::string original_package = std::move(attr->value);
- attr->value = package_override.to_string();
+ attr->value.assign(package_override);
xml::Element* application_el = manifest_el->FindChild({}, "application");
if (application_el != nullptr) {
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 8d1a647..7180ae6 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -61,12 +61,12 @@
.Build();
}
- std::unique_ptr<xml::XmlResource> Verify(const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> Verify(StringPiece str) {
return VerifyWithOptions(str, {});
}
- std::unique_ptr<xml::XmlResource> VerifyWithOptions(
- const StringPiece& str, const ManifestFixerOptions& options) {
+ std::unique_ptr<xml::XmlResource> VerifyWithOptions(StringPiece str,
+ const ManifestFixerOptions& options) {
std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(str);
ManifestFixer fixer(options);
if (fixer.Consume(mContext.get(), doc.get())) {
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index f2a93a8..9dadfb2 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -189,8 +189,7 @@
public:
EmptyDeclStack() = default;
- std::optional<xml::ExtractedPackage> TransformPackageAlias(
- const StringPiece& alias) const override {
+ std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
if (alias.empty()) {
return xml::ExtractedPackage{{}, true /*private*/};
}
@@ -206,8 +205,7 @@
: alias_namespaces_(std::move(namespaces)) {
}
- std::optional<xml::ExtractedPackage> TransformPackageAlias(
- const StringPiece& alias) const override {
+ std::optional<xml::ExtractedPackage> TransformPackageAlias(StringPiece alias) const override {
if (alias.empty()) {
return xml::ExtractedPackage{{}, true /*private*/};
}
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index c9f0964..67a4828 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -66,7 +66,7 @@
// This will merge and mangle resources from a static library. It is assumed that all FileReferences
// have correctly set their io::IFile*.
-bool TableMerger::MergeAndMangle(const android::Source& src, const StringPiece& package_name,
+bool TableMerger::MergeAndMangle(const android::Source& src, StringPiece package_name,
ResourceTable* table) {
bool error = false;
for (auto& package : table->packages) {
@@ -326,8 +326,8 @@
const std::string& package, const FileReference& file_ref) {
StringPiece prefix, entry, suffix;
if (util::ExtractResFilePathParts(*file_ref.path, &prefix, &entry, &suffix)) {
- std::string mangled_entry = NameMangler::MangleEntry(package, entry.to_string());
- std::string newPath = prefix.to_string() + mangled_entry + suffix.to_string();
+ std::string mangled_entry = NameMangler::MangleEntry(package, entry);
+ std::string newPath = (std::string(prefix) += mangled_entry) += suffix;
std::unique_ptr<FileReference> new_file_ref =
util::make_unique<FileReference>(main_table_->string_pool.MakeRef(newPath));
new_file_ref->SetComment(file_ref.GetComment());
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index 2ba2123..37daf42 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -61,7 +61,7 @@
// References are made to this ResourceTable for efficiency reasons.
TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options);
- inline const std::set<std::string>& merged_packages() const {
+ inline const std::set<std::string, std::less<>>& merged_packages() const {
return merged_packages_;
}
@@ -71,7 +71,7 @@
// Merges resources from the given package, mangling the name. This is for static libraries.
// All FileReference values must have their io::IFile set.
- bool MergeAndMangle(const android::Source& src, const android::StringPiece& package,
+ bool MergeAndMangle(const android::Source& src, android::StringPiece package,
ResourceTable* table);
// Merges a compiled file that belongs to this same or empty package.
@@ -84,7 +84,7 @@
ResourceTable* main_table_;
TableMergerOptions options_;
ResourceTablePackage* main_package_;
- std::set<std::string> merged_packages_;
+ std::set<std::string, std::less<>> merged_packages_;
bool MergeImpl(const android::Source& src, ResourceTable* src_table, bool overlay,
bool allow_new);
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index f994e27..f01db3d 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -113,12 +113,12 @@
};
class SignatureFilter : public IPathFilter {
- bool Keep(const std::string& path) override {
+ bool Keep(std::string_view path) override {
static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
- if (std::regex_search(path, signature_regex)) {
+ if (std::regex_search(path.begin(), path.end(), signature_regex)) {
return false;
}
- return !(path == "META-INF/MANIFEST.MF");
+ return path != "META-INF/MANIFEST.MF";
}
};
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index f704f26..cc21093 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -16,6 +16,8 @@
#include "optimize/Obfuscator.h"
+#include <fstream>
+#include <map>
#include <set>
#include <string>
#include <unordered_set>
@@ -32,10 +34,13 @@
namespace aapt {
-Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
+Obfuscator::Obfuscator(OptimizeOptions& optimizeOptions)
+ : options_(optimizeOptions.table_flattener_options),
+ shorten_resource_paths_(optimizeOptions.shorten_resource_paths),
+ collapse_key_stringpool_(optimizeOptions.table_flattener_options.collapse_key_stringpool) {
}
-std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
+std::string ShortenFileName(android::StringPiece file_path, int output_length) {
std::size_t hash_num = std::hash<android::StringPiece>{}(file_path);
std::string result = "";
// Convert to (modified) base64 so that it is a proper file path.
@@ -58,9 +63,9 @@
}
}
-std::string GetShortenedPath(const android::StringPiece& shortened_filename,
- const android::StringPiece& extension, int collision_count) {
- std::string shortened_path = "res/" + shortened_filename.to_string();
+std::string GetShortenedPath(android::StringPiece shortened_filename,
+ android::StringPiece extension, int collision_count) {
+ std::string shortened_path = std::string("res/") += shortened_filename;
if (collision_count > 0) {
shortened_path += std::to_string(collision_count);
}
@@ -77,7 +82,8 @@
}
};
-bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+static bool HandleShortenFilePaths(ResourceTable* table,
+ std::map<std::string, std::string>& shortened_path_map) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -109,10 +115,117 @@
shortened_path = GetShortenedPath(shortened_filename, extension, collision_count);
}
shortened_paths.insert(shortened_path);
- path_map_.insert({*file_ref->path, shortened_path});
+ shortened_path_map.insert({*file_ref->path, shortened_path});
file_ref->path = table->string_pool.MakeRef(shortened_path, file_ref->path.GetContext());
}
return true;
}
+void Obfuscator::ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result obfuscatedResult, const ResourceName&)>
+ onObfuscate) {
+ ResourceName resource_name({}, type_name, entry.name);
+ if (!collapse_key_stringpool ||
+ name_collapse_exemptions.find(resource_name) != name_collapse_exemptions.end()) {
+ onObfuscate(Result::Keep_ExemptionList, resource_name);
+ } else {
+ // resource isn't exempt from collapse, add it as obfuscated value
+ if (entry.overlayable_item) {
+ // if the resource name of the specific entry is obfuscated and this
+ // entry is in the overlayable list, the overlay can't work on this
+ // overlayable at runtime because the name has been obfuscated in
+ // resources.arsc during flatten operation.
+ onObfuscate(Result::Keep_Overlayable, resource_name);
+ } else {
+ onObfuscate(Result::Obfuscated, resource_name);
+ }
+ }
+}
+
+static bool HandleCollapseKeyStringPool(
+ const ResourceTable* table, const bool collapse_key_string_pool,
+ const std::set<ResourceName>& name_collapse_exemptions,
+ std::unordered_map<uint32_t, std::string>& id_resource_map) {
+ if (!collapse_key_string_pool) {
+ return true;
+ }
+
+ int entryResId = 0;
+ auto onObfuscate = [&entryResId, &id_resource_map](const Obfuscator::Result obfuscatedResult,
+ const ResourceName& resource_name) {
+ if (obfuscatedResult == Obfuscator::Result::Obfuscated) {
+ id_resource_map.insert({entryResId, resource_name.entry});
+ }
+ };
+
+ for (auto& package : table->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ if (!entry->id.has_value() || entry->name.empty()) {
+ continue;
+ }
+ entryResId = entry->id->id;
+ ResourceTableEntryView entry_view{
+ .name = entry->name,
+ .id = entry->id ? entry->id.value().entry_id() : (std::optional<uint16_t>)std::nullopt,
+ .visibility = entry->visibility,
+ .allow_new = entry->allow_new,
+ .overlayable_item = entry->overlayable_item,
+ .staged_id = entry->staged_id};
+
+ Obfuscator::ObfuscateResourceName(collapse_key_string_pool, name_collapse_exemptions,
+ type->named_type, entry_view, onObfuscate);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
+ HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
+ options_.name_collapse_exemptions, options_.id_resource_map);
+ if (shorten_resource_paths_) {
+ return HandleShortenFilePaths(table, options_.shortened_path_map);
+ }
+ return true;
+}
+
+bool Obfuscator::WriteObfuscationMap(const std::string& file_path) const {
+ pb::ResourceMappings resourceMappings;
+ for (const auto& [id, name] : options_.id_resource_map) {
+ auto* collapsedNameMapping = resourceMappings.mutable_collapsed_names()->add_resource_names();
+ collapsedNameMapping->set_id(id);
+ collapsedNameMapping->set_name(name);
+ }
+
+ for (const auto& [original_path, shortened_path] : options_.shortened_path_map) {
+ auto* resource_path = resourceMappings.mutable_shortened_paths()->add_resource_paths();
+ resource_path->set_original_path(original_path);
+ resource_path->set_shortened_path(shortened_path);
+ }
+
+ { // RAII style, output the pb content to file and close fout in destructor
+ std::ofstream fout(file_path, std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!fout.is_open()) {
+ return false;
+ }
+ return resourceMappings.SerializeToOstream(&fout);
+ }
+}
+
+/**
+ * Tell the optimizer whether it's needed to dump information for de-obfuscating.
+ *
+ * There are two conditions need to dump the information for de-obfuscating.
+ * * the option of shortening file paths is enabled.
+ * * the option of collapsing resource names is enabled.
+ * @return true if the information needed for de-obfuscating, otherwise false
+ */
+bool Obfuscator::IsEnabled() const {
+ return shorten_resource_paths_ || collapse_key_stringpool_;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/Obfuscator.h b/tools/aapt2/optimize/Obfuscator.h
index 1ea32db..5ccf5438 100644
--- a/tools/aapt2/optimize/Obfuscator.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -17,10 +17,15 @@
#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
-#include <map>
+#include <set>
#include <string>
+#include "ResourceMetadata.pb.h"
+#include "ResourceTable.h"
+#include "android-base/function_ref.h"
#include "android-base/macros.h"
+#include "cmd/Optimize.h"
+#include "format/binary/TableFlattener.h"
#include "process/IResourceTableConsumer.h"
namespace aapt {
@@ -30,12 +35,28 @@
// Maps resources in the apk to shortened paths.
class Obfuscator : public IResourceTableConsumer {
public:
- explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
+ explicit Obfuscator(OptimizeOptions& optimizeOptions);
bool Consume(IAaptContext* context, ResourceTable* table) override;
+ bool WriteObfuscationMap(const std::string& file_path) const;
+
+ bool IsEnabled() const;
+
+ enum class Result { Obfuscated, Keep_ExemptionList, Keep_Overlayable };
+
+ // hardcoded string uses characters which make it an invalid resource name
+ static constexpr char kObfuscatedResourceName[] = "0_resource_name_obfuscated";
+
+ static void ObfuscateResourceName(
+ const bool collapse_key_stringpool, const std::set<ResourceName>& name_collapse_exemptions,
+ const ResourceNamedType& type_name, const ResourceTableEntryView& entry,
+ const android::base::function_ref<void(Result, const ResourceName&)> onObfuscate);
+
private:
- std::map<std::string, std::string>& path_map_;
+ TableFlattenerOptions& options_;
+ const bool shorten_resource_paths_;
+ const bool collapse_key_stringpool_;
DISALLOW_COPY_AND_ASSIGN(Obfuscator);
};
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index a3339d4..7f57b71 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -16,14 +16,19 @@
#include "optimize/Obfuscator.h"
+#include <map>
#include <memory>
#include <string>
#include "ResourceTable.h"
+#include "android-base/file.h"
#include "test/Test.h"
using ::aapt::test::GetValue;
+using ::testing::AnyOf;
using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::IsTrue;
using ::testing::Not;
using ::testing::NotNull;
@@ -51,8 +56,9 @@
.AddString("android:string/string", "res/should/still/be/the/same.png")
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -87,8 +93,9 @@
test::ParseConfigOrDie("mdp-v21"))
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map to not contain the ColorStateList
ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
@@ -107,8 +114,9 @@
.AddFileReference("android:color/pngfile", original_png_path)
.Build();
- std::map<std::string, std::string> path_map;
- ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -133,8 +141,10 @@
test::ResourceTableBuilder builder1;
FillTable(builder1, 0, kNumResources);
std::unique_ptr<ResourceTable> table1 = builder1.Build();
- std::map<std::string, std::string> expected_mapping;
- ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
+ OptimizeOptions options{.shorten_resource_paths = true};
+ std::map<std::string, std::string>& expected_mapping =
+ options.table_flattener_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
// We are trying to ensure lack of non-determinism, it is not simple to prove
// a negative, thus we must try the test a few times so that the test itself
@@ -153,8 +163,10 @@
FillTable(builder2, 0, start_index);
std::unique_ptr<ResourceTable> table2 = builder2.Build();
- std::map<std::string, std::string> actual_mapping;
- ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
+ OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
+ TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
+ std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
+ ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
for (auto& item : actual_mapping) {
ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -162,4 +174,126 @@
}
}
+TEST(ObfuscatorTest, DumpIdResourceMap) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
+ overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
+ overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
+ overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
+
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ std::string name = "com.app.test:string/overlayable";
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .AddFileReference("android:color/xmlfile", original_xml_path)
+ .AddFileReference("android:color/pngfile", original_png_path)
+ .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+ aapt::util::make_unique<aapt::BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
+ .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
+ .AddString(name, ResourceId(0x7f030002), "HI")
+ .SetOverlayable(name, overlayable_item)
+ .Build();
+
+ OptimizeOptions options{.shorten_resource_paths = true};
+ TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+ flattenerOptions.collapse_key_stringpool = true;
+ flattenerOptions.name_collapse_exemptions.insert(
+ ResourceName({}, ResourceType::kString, "in_exemption"));
+ auto& id_resource_map = flattenerOptions.id_resource_map;
+ ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+ // Expect that the id resource name map is populated
+ EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
+ EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
+ EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
+ EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
+ OptimizeOptions options;
+ Obfuscator obfuscatorWithDefaultOption(options);
+ ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ Obfuscator obfuscatorWithShortenPathOption(options);
+ ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
+ OptimizeOptions options;
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscatorWithCollapseStringPoolOption(options);
+ ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
+}
+
+static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() {
+ std::string original_xml_path = "res/drawable/xmlfile.xml";
+ std::string original_png_path = "res/drawable/pngfile.png";
+
+ return test::ResourceTableBuilder()
+ .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path)
+ .AddFileReference("com.app.test:drawable/pngfile", original_png_path)
+ .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
+ aapt::util::make_unique<aapt::BinaryPrimitive>(
+ uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
+ .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world")
+ .Build();
+}
+
+TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
+ OptimizeOptions options{.shorten_resource_paths = true};
+ options.table_flattener_options.collapse_key_stringpool = true;
+ Obfuscator obfuscator(options);
+ ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+ getProtocolBufferTableUnderTest().get()));
+
+ obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+ std::string pbOut;
+ android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
+ EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
+ EXPECT_THAT(pbOut, HasSubstr("mycolor"));
+ EXPECT_THAT(pbOut, HasSubstr("mystring"));
+ pb::ResourceMappings resourceMappings;
+ EXPECT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
+ EXPECT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
+ auto& resource_names = resourceMappings.collapsed_names().resource_names();
+ EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+ EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
+ auto& shortened_paths = resourceMappings.shortened_paths();
+ EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2));
+ EXPECT_THAT(shortened_paths.resource_paths(0).original_path(),
+ AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+ EXPECT_THAT(shortened_paths.resource_paths(1).original_path(),
+ AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
+}
+
+TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
+ OptimizeOptions options;
+ Obfuscator obfuscator(options);
+ ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
+ getProtocolBufferTableUnderTest().get()));
+
+ obfuscator.WriteObfuscationMap("obfuscated_map.pb");
+
+ std::string pbOut;
+ android::base::ReadFileToString("obfuscated_map.pb", &pbOut, false /* follow_symlinks */);
+ ASSERT_THAT(pbOut, Eq(""));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/optimize/VersionCollapser_test.cpp b/tools/aapt2/optimize/VersionCollapser_test.cpp
index aa0d0c0..18dcd6b 100644
--- a/tools/aapt2/optimize/VersionCollapser_test.cpp
+++ b/tools/aapt2/optimize/VersionCollapser_test.cpp
@@ -23,7 +23,7 @@
namespace aapt {
static std::unique_ptr<ResourceTable> BuildTableWithConfigs(
- const StringPiece& name, std::initializer_list<std::string> list) {
+ StringPiece name, std::initializer_list<std::string> list) {
test::ResourceTableBuilder builder;
for (const std::string& item : list) {
builder.AddSimple(name, test::ParseConfigOrDie(item));
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 92b45c3..bca62da 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -218,7 +218,7 @@
return symbol;
}
-bool AssetManagerSymbolSource::AddAssetPath(const StringPiece& path) {
+bool AssetManagerSymbolSource::AddAssetPath(StringPiece path) {
TRACE_CALL();
if (std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(path.data())) {
apk_assets_.push_back(std::move(apk));
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index c17837c..b09ff70 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -192,7 +192,7 @@
public:
AssetManagerSymbolSource() = default;
- bool AddAssetPath(const android::StringPiece& path);
+ bool AddAssetPath(android::StringPiece path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 30336e2..65f63dc 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -34,61 +34,53 @@
namespace aapt {
namespace test {
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
- const ResourceId& id) {
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name, const ResourceId& id) {
return AddValue(name, id, util::make_unique<Id>());
}
-ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddSimple(StringPiece name,
const ConfigDescription& config,
const ResourceId& id) {
return AddValue(name, config, id, util::make_unique<Id>());
}
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
- const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, StringPiece ref) {
return AddReference(name, {}, ref);
}
-ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
- const ResourceId& id,
- const StringPiece& ref) {
+ResourceTableBuilder& ResourceTableBuilder::AddReference(StringPiece name, const ResourceId& id,
+ StringPiece ref) {
return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
}
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name,
- const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, StringPiece str) {
return AddString(name, {}, str);
}
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
- const StringPiece& str) {
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
+ StringPiece str) {
return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
-ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddString(StringPiece name, const ResourceId& id,
const ConfigDescription& config,
- const StringPiece& str) {
+ StringPiece str) {
return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
- const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
io::IFile* file) {
return AddFileReference(name, {}, path, file);
}
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
- const ResourceId& id,
- const StringPiece& path,
- io::IFile* file) {
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, const ResourceId& id,
+ StringPiece path, io::IFile* file) {
auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
file_ref->file = file;
return AddValue(name, id, std::move(file_ref));
}
-ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
- const StringPiece& path,
+ResourceTableBuilder& ResourceTableBuilder::AddFileReference(StringPiece name, StringPiece path,
const ConfigDescription& config,
io::IFile* file) {
auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
@@ -96,17 +88,17 @@
return AddValue(name, config, {}, std::move(file_ref));
}
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
std::unique_ptr<Value> value) {
return AddValue(name, {}, std::move(value));
}
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name, const ResourceId& id,
std::unique_ptr<Value> value) {
return AddValue(name, {}, id, std::move(value));
}
-ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::AddValue(StringPiece name,
const ConfigDescription& config,
const ResourceId& id,
std::unique_ptr<Value> value) {
@@ -121,8 +113,7 @@
return *this;
}
-ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
- const ResourceId& id,
+ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(StringPiece name, const ResourceId& id,
Visibility::Level level,
bool allow_new) {
ResourceName res_name = ParseNameOrDie(name);
@@ -136,9 +127,8 @@
return *this;
}
-ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(const StringPiece& name,
+ResourceTableBuilder& ResourceTableBuilder::SetOverlayable(StringPiece name,
const OverlayableItem& overlayable) {
-
ResourceName res_name = ParseNameOrDie(name);
CHECK(table_->AddResource(
NewResourceBuilder(res_name).SetOverlayable(overlayable).SetAllowMangled(true).Build(),
@@ -159,8 +149,7 @@
return std::move(table_);
}
-std::unique_ptr<Reference> BuildReference(const StringPiece& ref,
- const std::optional<ResourceId>& id) {
+std::unique_ptr<Reference> BuildReference(StringPiece ref, const std::optional<ResourceId>& id) {
std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref));
reference->id = id;
return reference;
@@ -188,7 +177,7 @@
return *this;
}
-AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
+AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) {
attr_->symbols.push_back(
Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
return *this;
@@ -198,17 +187,17 @@
return std::move(attr_);
}
-StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) {
+StyleBuilder& StyleBuilder::SetParent(StringPiece str) {
style_->parent = Reference(ParseNameOrDie(str));
return *this;
}
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, std::unique_ptr<Item> value) {
style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
return *this;
}
-StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id,
+StyleBuilder& StyleBuilder::AddItem(StringPiece str, const ResourceId& id,
std::unique_ptr<Item> value) {
AddItem(str, std::move(value));
style_->entries.back().key.id = id;
@@ -219,8 +208,7 @@
return std::move(style_);
}
-StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str,
- const std::optional<ResourceId>& id) {
+StyleableBuilder& StyleableBuilder::AddItem(StringPiece str, const std::optional<ResourceId>& id) {
styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
styleable_->entries.back().id = id;
return *this;
@@ -230,7 +218,7 @@
return std::move(styleable_);
}
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
+std::unique_ptr<xml::XmlResource> BuildXmlDom(StringPiece str) {
std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
input.append(str.data(), str.size());
StringInputStream in(input);
@@ -241,7 +229,7 @@
}
std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
- const StringPiece& str) {
+ StringPiece str) {
std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
doc->file.name.package = context->GetCompilationPackage();
return doc;
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 780bd0d..f03d6fc 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -38,40 +38,35 @@
public:
ResourceTableBuilder() = default;
- ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
- ResourceTableBuilder& AddSimple(const android::StringPiece& name,
+ ResourceTableBuilder& AddSimple(android::StringPiece name, const ResourceId& id = {});
+ ResourceTableBuilder& AddSimple(android::StringPiece name,
const android::ConfigDescription& config,
const ResourceId& id = {});
- ResourceTableBuilder& AddReference(const android::StringPiece& name,
- const android::StringPiece& ref);
- ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
- const android::StringPiece& ref);
- ResourceTableBuilder& AddString(const android::StringPiece& name,
- const android::StringPiece& str);
- ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
- const android::StringPiece& str);
- ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
+ ResourceTableBuilder& AddReference(android::StringPiece name, android::StringPiece ref);
+ ResourceTableBuilder& AddReference(android::StringPiece name, const ResourceId& id,
+ android::StringPiece ref);
+ ResourceTableBuilder& AddString(android::StringPiece name, android::StringPiece str);
+ ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
+ android::StringPiece str);
+ ResourceTableBuilder& AddString(android::StringPiece name, const ResourceId& id,
const android::ConfigDescription& config,
- const android::StringPiece& str);
- ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
- const android::StringPiece& path,
+ android::StringPiece str);
+ ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
io::IFile* file = nullptr);
- ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
- const android::StringPiece& path,
- io::IFile* file = nullptr);
- ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
- const android::StringPiece& path,
+ ResourceTableBuilder& AddFileReference(android::StringPiece name, const ResourceId& id,
+ android::StringPiece path, io::IFile* file = nullptr);
+ ResourceTableBuilder& AddFileReference(android::StringPiece name, android::StringPiece path,
const android::ConfigDescription& config,
io::IFile* file = nullptr);
- ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
- ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
+ ResourceTableBuilder& AddValue(android::StringPiece name, std::unique_ptr<Value> value);
+ ResourceTableBuilder& AddValue(android::StringPiece name, const ResourceId& id,
std::unique_ptr<Value> value);
- ResourceTableBuilder& AddValue(const android::StringPiece& name,
- const android::ConfigDescription& config,
- const ResourceId& id, std::unique_ptr<Value> value);
- ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
+ ResourceTableBuilder& AddValue(android::StringPiece name,
+ const android::ConfigDescription& config, const ResourceId& id,
+ std::unique_ptr<Value> value);
+ ResourceTableBuilder& SetSymbolState(android::StringPiece name, const ResourceId& id,
Visibility::Level level, bool allow_new = false);
- ResourceTableBuilder& SetOverlayable(const android::StringPiece& name,
+ ResourceTableBuilder& SetOverlayable(android::StringPiece name,
const OverlayableItem& overlayable);
ResourceTableBuilder& Add(NewResource&& res);
@@ -84,7 +79,7 @@
std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
};
-std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
+std::unique_ptr<Reference> BuildReference(android::StringPiece ref,
const std::optional<ResourceId>& id = {});
std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);
@@ -101,7 +96,7 @@
return *this;
}
- ValueBuilder& SetComment(const android::StringPiece& str) {
+ ValueBuilder& SetComment(android::StringPiece str) {
value_->SetComment(str);
return *this;
}
@@ -121,7 +116,7 @@
AttributeBuilder();
AttributeBuilder& SetTypeMask(uint32_t typeMask);
AttributeBuilder& SetWeak(bool weak);
- AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
+ AttributeBuilder& AddItem(android::StringPiece name, uint32_t value);
std::unique_ptr<Attribute> Build();
private:
@@ -133,9 +128,9 @@
class StyleBuilder {
public:
StyleBuilder() = default;
- StyleBuilder& SetParent(const android::StringPiece& str);
- StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);
- StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
+ StyleBuilder& SetParent(android::StringPiece str);
+ StyleBuilder& AddItem(android::StringPiece str, std::unique_ptr<Item> value);
+ StyleBuilder& AddItem(android::StringPiece str, const ResourceId& id,
std::unique_ptr<Item> value);
std::unique_ptr<Style> Build();
@@ -148,8 +143,7 @@
class StyleableBuilder {
public:
StyleableBuilder() = default;
- StyleableBuilder& AddItem(const android::StringPiece& str,
- const std::optional<ResourceId>& id = {});
+ StyleableBuilder& AddItem(android::StringPiece str, const std::optional<ResourceId>& id = {});
std::unique_ptr<Styleable> Build();
private:
@@ -158,9 +152,9 @@
std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
};
-std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
+std::unique_ptr<xml::XmlResource> BuildXmlDom(android::StringPiece str);
std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
- const android::StringPiece& str);
+ android::StringPiece str);
class ArtifactBuilder {
public:
diff --git a/tools/aapt2/test/Common.cpp b/tools/aapt2/test/Common.cpp
index eca0c1c..cdf24534 100644
--- a/tools/aapt2/test/Common.cpp
+++ b/tools/aapt2/test/Common.cpp
@@ -44,10 +44,9 @@
}
template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
- const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
const ConfigDescription& config,
- const android::StringPiece& product) {
+ android::StringPiece product) {
std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
if (result) {
ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 3f28361..83a0f3f 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -39,22 +39,22 @@
android::IDiagnostics* GetDiagnostics();
-inline ResourceName ParseNameOrDie(const android::StringPiece& str) {
+inline ResourceName ParseNameOrDie(android::StringPiece str) {
ResourceNameRef ref;
CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str;
return ref.ToResourceName();
}
-inline android::ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
- android::ConfigDescription config;
+inline android::ConfigDescription ParseConfigOrDie(android::StringPiece str) {
+ android::ConfigDescription config;
CHECK(android::ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
return config;
}
template <typename T = Value>
-T* GetValueForConfigAndProduct(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfigAndProduct(ResourceTable* table, android::StringPiece res_name,
const android::ConfigDescription& config,
- const android::StringPiece& product) {
+ android::StringPiece product) {
std::optional<ResourceTable::SearchResult> result = table->FindResource(ParseNameOrDie(res_name));
if (result) {
ResourceConfigValue* config_value = result.value().entry->FindValue(config, product);
@@ -66,25 +66,25 @@
}
template <>
-Value* GetValueForConfigAndProduct<Value>(ResourceTable* table,
- const android::StringPiece& res_name,
+Value* GetValueForConfigAndProduct<Value>(ResourceTable* table, android::StringPiece res_name,
const android::ConfigDescription& config,
- const android::StringPiece& product);
+ android::StringPiece product);
template <typename T = Value>
-T* GetValueForConfig(ResourceTable* table, const android::StringPiece& res_name,
+T* GetValueForConfig(ResourceTable* table, android::StringPiece res_name,
const android::ConfigDescription& config) {
return GetValueForConfigAndProduct<T>(table, res_name, config, {});
}
template <typename T = Value>
-T* GetValue(ResourceTable* table, const android::StringPiece& res_name) {
+T* GetValue(ResourceTable* table, android::StringPiece res_name) {
return GetValueForConfig<T>(table, res_name, {});
}
class TestFile : public io::IFile {
public:
- explicit TestFile(const android::StringPiece& path) : source_(path) {}
+ explicit TestFile(android::StringPiece path) : source_(path) {
+ }
std::unique_ptr<io::IData> OpenAsData() override {
return {};
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4e4973e..c5331fb 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -52,8 +52,8 @@
return compilation_package_.value();
}
- void SetCompilationPackage(const android::StringPiece& package) {
- compilation_package_ = package.to_string();
+ void SetCompilationPackage(android::StringPiece package) {
+ compilation_package_ = std::string(package);
}
uint8_t GetPackageId() override {
@@ -111,8 +111,8 @@
return *this;
}
- ContextBuilder& SetCompilationPackage(const android::StringPiece& package) {
- context_->compilation_package_ = package.to_string();
+ ContextBuilder& SetCompilationPackage(android::StringPiece package) {
+ context_->compilation_package_ = std::string(package);
return *this;
}
@@ -149,7 +149,7 @@
class StaticSymbolSourceBuilder {
public:
- StaticSymbolSourceBuilder& AddPublicSymbol(const android::StringPiece& name, ResourceId id,
+ StaticSymbolSourceBuilder& AddPublicSymbol(android::StringPiece name, ResourceId id,
std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<SymbolTable::Symbol> symbol =
util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true);
@@ -159,7 +159,7 @@
return *this;
}
- StaticSymbolSourceBuilder& AddSymbol(const android::StringPiece& name, ResourceId id,
+ StaticSymbolSourceBuilder& AddSymbol(android::StringPiece name, ResourceId id,
std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<SymbolTable::Symbol> symbol =
util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false);
diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp
index dbc0e36..428372f 100644
--- a/tools/aapt2/test/Fixture.cpp
+++ b/tools/aapt2/test/Fixture.cpp
@@ -38,8 +38,8 @@
const char* CommandTestFixture::kDefaultPackageName = "com.aapt.command.test";
-void ClearDirectory(const android::StringPiece& path) {
- const std::string root_dir = path.to_string();
+void ClearDirectory(android::StringPiece path) {
+ const std::string root_dir(path);
std::unique_ptr<DIR, decltype(closedir)*> dir(opendir(root_dir.data()), closedir);
if (!dir) {
StdErrDiagnostics().Error(android::DiagMessage()
@@ -91,8 +91,7 @@
}
bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents,
- const android::StringPiece& out_dir,
- android::IDiagnostics* diag) {
+ android::StringPiece out_dir, android::IDiagnostics* diag) {
WriteFile(path, contents);
CHECK(file::mkdirs(out_dir.data()));
return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0;
@@ -113,8 +112,8 @@
return LinkCommand(diag).Execute(link_args, &std::cerr) == 0;
}
-bool CommandTestFixture::Link(const std::vector<std::string>& args,
- const android::StringPiece& flat_dir, android::IDiagnostics* diag) {
+bool CommandTestFixture::Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
+ android::IDiagnostics* diag) {
std::vector<android::StringPiece> link_args;
for(const std::string& arg : args) {
link_args.emplace_back(arg);
@@ -148,7 +147,7 @@
}
std::unique_ptr<io::IData> CommandTestFixture::OpenFileAsData(LoadedApk* apk,
- const android::StringPiece& path) {
+ android::StringPiece path) {
return apk
->GetFileCollection()
->FindFile(path)
diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h
index 61403b7..ba4a734 100644
--- a/tools/aapt2/test/Fixture.h
+++ b/tools/aapt2/test/Fixture.h
@@ -48,7 +48,7 @@
// Retrieves the absolute path of the specified relative path in the test directory. Directories
// should be separated using forward slashes ('/'), and these slashes will be translated to
// backslashes when running Windows tests.
- std::string GetTestPath(const android::StringPiece& path) {
+ std::string GetTestPath(android::StringPiece path) {
std::string base = temp_dir_;
for (android::StringPiece part : util::Split(path, '/')) {
file::AppendPath(&base, part);
@@ -73,22 +73,21 @@
// Wries the contents of the file to the specified path. The file is compiled and the flattened
// file is written to the out directory.
bool CompileFile(const std::string& path, const std::string& contents,
- const android::StringPiece& flat_out_dir, android::IDiagnostics* diag);
+ android::StringPiece flat_out_dir, android::IDiagnostics* diag);
// Executes the link command with the specified arguments.
bool Link(const std::vector<std::string>& args, android::IDiagnostics* diag);
// Executes the link command with the specified arguments. The flattened files residing in the
// flat directory will be added to the link command as file arguments.
- bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir,
+ bool Link(const std::vector<std::string>& args, android::StringPiece flat_dir,
android::IDiagnostics* diag);
// Creates a minimal android manifest within the test directory and returns the file path.
std::string GetDefaultManifest(const char* package_name = kDefaultPackageName);
// Returns pointer to data inside APK files
- std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk,
- const android::StringPiece& path);
+ std::unique_ptr<io::IData> OpenFileAsData(LoadedApk* apk, android::StringPiece path);
// Asserts that loading the tree from the specified file in the apk succeeds.
void AssertLoadXml(LoadedApk* apk, const io::IData* data,
diff --git a/tools/aapt2/text/Printer.cpp b/tools/aapt2/text/Printer.cpp
index 243800c..8e491ac 100644
--- a/tools/aapt2/text/Printer.cpp
+++ b/tools/aapt2/text/Printer.cpp
@@ -26,7 +26,7 @@
namespace aapt {
namespace text {
-Printer& Printer::Println(const StringPiece& str) {
+Printer& Printer::Println(StringPiece str) {
Print(str);
return Print("\n");
}
@@ -35,7 +35,7 @@
return Print("\n");
}
-Printer& Printer::Print(const StringPiece& str) {
+Printer& Printer::Print(StringPiece str) {
if (error_) {
return *this;
}
@@ -47,7 +47,7 @@
const auto new_line_iter = std::find(remaining_str_begin, remaining_str_end, '\n');
// We will copy the string up until the next new-line (or end of string).
- const StringPiece str_to_copy = str.substr(remaining_str_begin, new_line_iter);
+ const StringPiece str_to_copy(remaining_str_begin, new_line_iter - remaining_str_begin);
if (!str_to_copy.empty()) {
if (needs_indent_) {
for (int i = 0; i < indent_level_; i++) {
diff --git a/tools/aapt2/text/Printer.h b/tools/aapt2/text/Printer.h
index f399f8e..f7ad98b 100644
--- a/tools/aapt2/text/Printer.h
+++ b/tools/aapt2/text/Printer.h
@@ -31,8 +31,8 @@
explicit Printer(::aapt::io::OutputStream* out) : out_(out) {
}
- Printer& Print(const ::android::StringPiece& str);
- Printer& Println(const ::android::StringPiece& str);
+ Printer& Print(android::StringPiece str);
+ Printer& Println(android::StringPiece str);
Printer& Println();
void Indent();
diff --git a/tools/aapt2/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp
index 3735b3e..5e25be3 100644
--- a/tools/aapt2/text/Unicode.cpp
+++ b/tools/aapt2/text/Unicode.cpp
@@ -77,7 +77,7 @@
(codepoint == 0x3000);
}
-bool IsJavaIdentifier(const StringPiece& str) {
+bool IsJavaIdentifier(StringPiece str) {
Utf8Iterator iter(str);
// Check the first character.
@@ -99,7 +99,7 @@
return true;
}
-bool IsValidResourceEntryName(const StringPiece& str) {
+bool IsValidResourceEntryName(StringPiece str) {
Utf8Iterator iter(str);
// Check the first character.
diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h
index 546714e..ab3e82b 100644
--- a/tools/aapt2/text/Unicode.h
+++ b/tools/aapt2/text/Unicode.h
@@ -46,11 +46,11 @@
// Returns true if the UTF8 string can be used as a Java identifier.
// NOTE: This does not check against the set of reserved Java keywords.
-bool IsJavaIdentifier(const android::StringPiece& str);
+bool IsJavaIdentifier(android::StringPiece str);
// Returns true if the UTF8 string can be used as the entry name of a resource name.
// This is the `entry` part of package:type/entry.
-bool IsValidResourceEntryName(const android::StringPiece& str);
+bool IsValidResourceEntryName(android::StringPiece str);
} // namespace text
} // namespace aapt
diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp
index 20b9073..0bd8a37 100644
--- a/tools/aapt2/text/Utf8Iterator.cpp
+++ b/tools/aapt2/text/Utf8Iterator.cpp
@@ -24,7 +24,7 @@
namespace aapt {
namespace text {
-Utf8Iterator::Utf8Iterator(const StringPiece& str)
+Utf8Iterator::Utf8Iterator(StringPiece str)
: str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) {
DoNext();
}
diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h
index 9318401..2bba198 100644
--- a/tools/aapt2/text/Utf8Iterator.h
+++ b/tools/aapt2/text/Utf8Iterator.h
@@ -25,7 +25,7 @@
class Utf8Iterator {
public:
- explicit Utf8Iterator(const android::StringPiece& str);
+ explicit Utf8Iterator(android::StringPiece str);
bool HasNext() const;
diff --git a/tools/aapt2/trace/TraceBuffer.cpp b/tools/aapt2/trace/TraceBuffer.cpp
index b4b31d9..da53739 100644
--- a/tools/aapt2/trace/TraceBuffer.cpp
+++ b/tools/aapt2/trace/TraceBuffer.cpp
@@ -103,7 +103,7 @@
s << tag;
s << " ";
for (auto& arg : args) {
- s << arg.to_string();
+ s << arg;
s << " ";
}
tracebuffer::Add(s.str(), tracebuffer::kBegin);
@@ -124,7 +124,7 @@
s << tag;
s << " ";
for (auto& arg : args) {
- s << arg.to_string();
+ s << arg;
s << " ";
}
tracebuffer::Add(s.str(), tracebuffer::kBegin);
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index 5d5b7cd..93c1b61 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -139,7 +139,7 @@
return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST;
}
-StringPiece GetStem(const StringPiece& path) {
+StringPiece GetStem(StringPiece path) {
const char* start = path.begin();
const char* end = path.end();
for (const char* current = end - 1; current != start - 1; --current) {
@@ -150,7 +150,7 @@
return {};
}
-StringPiece GetFilename(const StringPiece& path) {
+StringPiece GetFilename(StringPiece path) {
const char* end = path.end();
const char* last_dir_sep = path.begin();
for (const char* c = path.begin(); c != end; ++c) {
@@ -161,7 +161,7 @@
return StringPiece(last_dir_sep, end - last_dir_sep);
}
-StringPiece GetExtension(const StringPiece& path) {
+StringPiece GetExtension(StringPiece path) {
StringPiece filename = GetFilename(path);
const char* const end = filename.end();
const char* c = std::find(filename.begin(), end, '.');
@@ -171,7 +171,7 @@
return {};
}
-bool IsHidden(const android::StringPiece& path) {
+bool IsHidden(android::StringPiece path) {
return util::StartsWith(GetFilename(path), ".");
}
@@ -193,16 +193,16 @@
if (args.empty()) {
return "";
}
- std::string out = args[0].to_string();
+ std::string out{args[0]};
for (int i = 1; i < args.size(); i++) {
file::AppendPath(&out, args[i]);
}
return out;
}
-std::string PackageToPath(const StringPiece& package) {
+std::string PackageToPath(StringPiece package) {
std::string out_path;
- for (const StringPiece& part : util::Tokenize(package, '.')) {
+ for (StringPiece part : util::Tokenize(package, '.')) {
AppendPath(&out_path, part);
}
return out_path;
@@ -241,10 +241,10 @@
return std::move(filemap);
}
-bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist,
std::string* out_error) {
std::string contents;
- if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+ if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
if (out_error) {
*out_error = "failed to read argument-list file";
}
@@ -254,16 +254,16 @@
for (StringPiece line : util::Tokenize(contents, ' ')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- out_arglist->push_back(line.to_string());
+ out_arglist->emplace_back(line);
}
}
return true;
}
-bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set<std::string>* out_argset,
+bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset,
std::string* out_error) {
std::string contents;
- if(!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
+ if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) {
if (out_error) {
*out_error = "failed to read argument-list file";
}
@@ -273,13 +273,13 @@
for (StringPiece line : util::Tokenize(contents, ' ')) {
line = util::TrimWhitespace(line);
if (!line.empty()) {
- out_argset->insert(line.to_string());
+ out_argset->emplace(line);
}
}
return true;
}
-bool FileFilter::SetPattern(const StringPiece& pattern) {
+bool FileFilter::SetPattern(StringPiece pattern) {
pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
return true;
}
@@ -343,10 +343,10 @@
return true;
}
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
android::IDiagnostics* diag,
const FileFilter* filter) {
- const std::string root_dir = path.to_string();
+ const auto& root_dir = path;
std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
if (!d) {
diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir);
@@ -361,7 +361,7 @@
}
std::string file_name = entry->d_name;
- std::string full_path = root_dir;
+ std::string full_path{root_dir};
AppendPath(&full_path, file_name);
const FileType file_type = GetFileType(full_path);
@@ -380,7 +380,7 @@
// Now process subdirs.
for (const std::string& subdir : subdirs) {
- std::string full_subdir = root_dir;
+ std::string full_subdir{root_dir};
AppendPath(&full_subdir, subdir);
std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
if (!subfiles) {
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index ee95712..42eeaf2 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -66,31 +66,31 @@
bool mkdirs(const std::string& path);
// Returns all but the last part of the path.
-android::StringPiece GetStem(const android::StringPiece& path);
+android::StringPiece GetStem(android::StringPiece path);
// Returns the last part of the path with extension.
-android::StringPiece GetFilename(const android::StringPiece& path);
+android::StringPiece GetFilename(android::StringPiece path);
// Returns the extension of the path. This is the entire string after the first '.' of the last part
// of the path.
-android::StringPiece GetExtension(const android::StringPiece& path);
+android::StringPiece GetExtension(android::StringPiece path);
// Returns whether or not the name of the file or directory is a hidden file name
-bool IsHidden(const android::StringPiece& path);
+bool IsHidden(android::StringPiece path);
// Converts a package name (com.android.app) to a path: com/android/app
-std::string PackageToPath(const android::StringPiece& package);
+std::string PackageToPath(android::StringPiece package);
// Creates a FileMap for the file at path.
std::optional<android::FileMap> MmapPath(const std::string& path, std::string* out_error);
// Reads the file at path and appends each line to the outArgList vector.
-bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist,
+bool AppendArgsFromFile(android::StringPiece path, std::vector<std::string>* out_arglist,
std::string* out_error);
// Reads the file at path and appends each line to the outargset set.
-bool AppendSetArgsFromFile(const android::StringPiece& path,
- std::unordered_set<std::string>* out_argset, std::string* out_error);
+bool AppendSetArgsFromFile(android::StringPiece path, std::unordered_set<std::string>* out_argset,
+ std::string* out_error);
// Filter that determines which resource files/directories are
// processed by AAPT. Takes a pattern string supplied by the user.
@@ -112,7 +112,7 @@
// - The special filenames "." and ".." are always ignored.
// - Otherwise the full string is matched.
// - match is not case-sensitive.
- bool SetPattern(const android::StringPiece& pattern);
+ bool SetPattern(android::StringPiece pattern);
// Applies the filter, returning true for pass, false for fail.
bool operator()(const std::string& filename, FileType type) const;
@@ -126,7 +126,7 @@
// Returns a list of files relative to the directory identified by `path`.
// An optional FileFilter filters out any files that don't pass.
-std::optional<std::vector<std::string>> FindFiles(const android::StringPiece& path,
+std::optional<std::vector<std::string>> FindFiles(android::StringPiece path,
android::IDiagnostics* diag,
const FileFilter* filter = nullptr);
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 9b7ebdd..be87766 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -43,15 +43,14 @@
// See frameworks/base/core/java/android/content/pm/parsing/ParsingPackageUtils.java
constexpr static const size_t kMaxPackageNameSize = 223;
-static std::vector<std::string> SplitAndTransform(
- const StringPiece& str, char sep, const std::function<char(char)>& f) {
+static std::vector<std::string> SplitAndTransform(StringPiece str, char sep, char (*f)(char)) {
std::vector<std::string> parts;
const StringPiece::const_iterator end = std::end(str);
StringPiece::const_iterator start = std::begin(str);
StringPiece::const_iterator current;
do {
current = std::find(start, end, sep);
- parts.emplace_back(str.substr(start, current).to_string());
+ parts.emplace_back(start, current);
if (f) {
std::string& part = parts.back();
std::transform(part.begin(), part.end(), part.begin(), f);
@@ -61,29 +60,29 @@
return parts;
}
-std::vector<std::string> Split(const StringPiece& str, char sep) {
+std::vector<std::string> Split(StringPiece str, char sep) {
return SplitAndTransform(str, sep, nullptr);
}
-std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
- return SplitAndTransform(str, sep, ::tolower);
+std::vector<std::string> SplitAndLowercase(StringPiece str, char sep) {
+ return SplitAndTransform(str, sep, [](char c) -> char { return ::tolower(c); });
}
-bool StartsWith(const StringPiece& str, const StringPiece& prefix) {
+bool StartsWith(StringPiece str, StringPiece prefix) {
if (str.size() < prefix.size()) {
return false;
}
return str.substr(0, prefix.size()) == prefix;
}
-bool EndsWith(const StringPiece& str, const StringPiece& suffix) {
+bool EndsWith(StringPiece str, StringPiece suffix) {
if (str.size() < suffix.size()) {
return false;
}
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
}
-StringPiece TrimLeadingWhitespace(const StringPiece& str) {
+StringPiece TrimLeadingWhitespace(StringPiece str) {
if (str.size() == 0 || str.data() == nullptr) {
return str;
}
@@ -97,7 +96,7 @@
return StringPiece(start, end - start);
}
-StringPiece TrimTrailingWhitespace(const StringPiece& str) {
+StringPiece TrimTrailingWhitespace(StringPiece str) {
if (str.size() == 0 || str.data() == nullptr) {
return str;
}
@@ -111,7 +110,7 @@
return StringPiece(start, end - start);
}
-StringPiece TrimWhitespace(const StringPiece& str) {
+StringPiece TrimWhitespace(StringPiece str) {
if (str.size() == 0 || str.data() == nullptr) {
return str;
}
@@ -130,9 +129,9 @@
return StringPiece(start, end - start);
}
-static int IsJavaNameImpl(const StringPiece& str) {
+static int IsJavaNameImpl(StringPiece str) {
int pieces = 0;
- for (const StringPiece& piece : Tokenize(str, '.')) {
+ for (StringPiece piece : Tokenize(str, '.')) {
pieces++;
if (!text::IsJavaIdentifier(piece)) {
return -1;
@@ -141,17 +140,17 @@
return pieces;
}
-bool IsJavaClassName(const StringPiece& str) {
+bool IsJavaClassName(StringPiece str) {
return IsJavaNameImpl(str) >= 2;
}
-bool IsJavaPackageName(const StringPiece& str) {
+bool IsJavaPackageName(StringPiece str) {
return IsJavaNameImpl(str) >= 1;
}
-static int IsAndroidNameImpl(const StringPiece& str) {
+static int IsAndroidNameImpl(StringPiece str) {
int pieces = 0;
- for (const StringPiece& piece : Tokenize(str, '.')) {
+ for (StringPiece piece : Tokenize(str, '.')) {
if (piece.empty()) {
return -1;
}
@@ -173,15 +172,14 @@
return pieces;
}
-bool IsAndroidPackageName(const StringPiece& str) {
+bool IsAndroidPackageName(StringPiece str) {
if (str.size() > kMaxPackageNameSize) {
return false;
}
return IsAndroidNameImpl(str) > 1 || str == "android";
}
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
- const android::StringPiece& shared_user_id) {
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id) {
if (shared_user_id.size() > kMaxPackageNameSize) {
return false;
}
@@ -189,25 +187,24 @@
package_name == "android";
}
-bool IsAndroidSplitName(const StringPiece& str) {
+bool IsAndroidSplitName(StringPiece str) {
return IsAndroidNameImpl(str) > 0;
}
-std::optional<std::string> GetFullyQualifiedClassName(const StringPiece& package,
- const StringPiece& classname) {
+std::optional<std::string> GetFullyQualifiedClassName(StringPiece package, StringPiece classname) {
if (classname.empty()) {
return {};
}
if (util::IsJavaClassName(classname)) {
- return classname.to_string();
+ return std::string(classname);
}
if (package.empty()) {
return {};
}
- std::string result = package.to_string();
+ std::string result{package};
if (classname.data()[0] != '.') {
result += '.';
}
@@ -251,7 +248,7 @@
return static_cast<size_t>(c - start);
}
-bool VerifyJavaStringFormat(const StringPiece& str) {
+bool VerifyJavaStringFormat(StringPiece str) {
const char* c = str.begin();
const char* const end = str.end();
@@ -341,7 +338,7 @@
return true;
}
-std::u16string Utf8ToUtf16(const StringPiece& utf8) {
+std::u16string Utf8ToUtf16(StringPiece utf8) {
ssize_t utf16_length = utf8_to_utf16_length(
reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length());
if (utf16_length <= 0) {
@@ -381,7 +378,7 @@
const char* end = str_.end();
if (start == end) {
end_ = true;
- token_.assign(token_.end(), 0);
+ token_ = StringPiece(token_.end(), 0);
return *this;
}
@@ -389,12 +386,12 @@
const char* current = start;
while (current != end) {
if (*current == separator_) {
- token_.assign(start, current - start);
+ token_ = StringPiece(start, current - start);
return *this;
}
++current;
}
- token_.assign(start, end - start);
+ token_ = StringPiece(start, end - start);
return *this;
}
@@ -409,15 +406,17 @@
return !(*this == rhs);
}
-Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end)
- : str_(s), separator_(sep), token_(tok), end_(end) {}
+Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, bool end)
+ : str_(s), separator_(sep), token_(tok), end_(end) {
+}
-Tokenizer::Tokenizer(const StringPiece& str, char sep)
+Tokenizer::Tokenizer(StringPiece str, char sep)
: begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)),
- end_(str, sep, StringPiece(str.end(), 0), true) {}
+ end_(str, sep, StringPiece(str.end(), 0), true) {
+}
-bool ExtractResFilePathParts(const StringPiece& path, StringPiece* out_prefix,
- StringPiece* out_entry, StringPiece* out_suffix) {
+bool ExtractResFilePathParts(StringPiece path, StringPiece* out_prefix, StringPiece* out_entry,
+ StringPiece* out_suffix) {
const StringPiece res_prefix("res/");
if (!StartsWith(path, res_prefix)) {
return false;
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 8d3b413..40ff5b6 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -48,44 +48,44 @@
T end;
};
-std::vector<std::string> Split(const android::StringPiece& str, char sep);
-std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+std::vector<std::string> Split(android::StringPiece str, char sep);
+std::vector<std::string> SplitAndLowercase(android::StringPiece str, char sep);
// Returns true if the string starts with prefix.
-bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
+bool StartsWith(android::StringPiece str, android::StringPiece prefix);
// Returns true if the string ends with suffix.
-bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
+bool EndsWith(android::StringPiece str, android::StringPiece suffix);
// Creates a new StringPiece that points to a substring of the original string without leading
// whitespace.
-android::StringPiece TrimLeadingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimLeadingWhitespace(android::StringPiece str);
// Creates a new StringPiece that points to a substring of the original string without trailing
// whitespace.
-android::StringPiece TrimTrailingWhitespace(const android::StringPiece& str);
+android::StringPiece TrimTrailingWhitespace(android::StringPiece str);
// Creates a new StringPiece that points to a substring of the original string without leading or
// trailing whitespace.
-android::StringPiece TrimWhitespace(const android::StringPiece& str);
+android::StringPiece TrimWhitespace(android::StringPiece str);
// Tests that the string is a valid Java class name.
-bool IsJavaClassName(const android::StringPiece& str);
+bool IsJavaClassName(android::StringPiece str);
// Tests that the string is a valid Java package name.
-bool IsJavaPackageName(const android::StringPiece& str);
+bool IsJavaPackageName(android::StringPiece str);
// Tests that the string is a valid Android package name. More strict than a Java package name.
// - First character of each component (separated by '.') must be an ASCII letter.
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
// - Package must contain at least two components, unless it is 'android'.
// - The maximum package name length is 223.
-bool IsAndroidPackageName(const android::StringPiece& str);
+bool IsAndroidPackageName(android::StringPiece str);
// Tests that the string is a valid Android split name.
// - First character of each component (separated by '.') must be an ASCII letter.
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
-bool IsAndroidSplitName(const android::StringPiece& str);
+bool IsAndroidSplitName(android::StringPiece str);
// Tests that the string is a valid Android shared user id.
// - First character of each component (separated by '.') must be an ASCII letter.
@@ -93,8 +93,7 @@
// - Must contain at least two components, unless package name is 'android'.
// - The maximum shared user id length is 223.
// - Treat empty string as valid, it's the case of no shared user id.
-bool IsAndroidSharedUserId(const android::StringPiece& package_name,
- const android::StringPiece& shared_user_id);
+bool IsAndroidSharedUserId(android::StringPiece package_name, android::StringPiece shared_user_id);
// Converts the class name to a fully qualified class name from the given
// `package`. Ex:
@@ -103,8 +102,8 @@
// .asdf --> package.asdf
// .a.b --> package.a.b
// asdf.adsf --> asdf.adsf
-std::optional<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
- const android::StringPiece& class_name);
+std::optional<std::string> GetFullyQualifiedClassName(android::StringPiece package,
+ android::StringPiece class_name);
// Retrieves the formatted name of aapt2.
const char* GetToolName();
@@ -152,16 +151,16 @@
// explicitly specifying an index) when there are more than one argument. This is an error
// because translations may rearrange the order of the arguments in the string, which will
// break the string interpolation.
-bool VerifyJavaStringFormat(const android::StringPiece& str);
+bool VerifyJavaStringFormat(android::StringPiece str);
-bool AppendStyledString(const android::StringPiece& input, bool preserve_spaces,
- std::string* out_str, std::string* out_error);
+bool AppendStyledString(android::StringPiece input, bool preserve_spaces, std::string* out_str,
+ std::string* out_error);
class StringBuilder {
public:
StringBuilder() = default;
- StringBuilder& Append(const android::StringPiece& str);
+ StringBuilder& Append(android::StringPiece str);
const std::string& ToString() const;
const std::string& Error() const;
bool IsEmpty() const;
@@ -229,7 +228,7 @@
private:
friend class Tokenizer;
- iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end);
+ iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end);
android::StringPiece str_;
char separator_;
@@ -237,7 +236,7 @@
bool end_;
};
- Tokenizer(const android::StringPiece& str, char sep);
+ Tokenizer(android::StringPiece str, char sep);
iterator begin() const {
return begin_;
@@ -252,7 +251,7 @@
const iterator end_;
};
-inline Tokenizer Tokenize(const android::StringPiece& str, char sep) {
+inline Tokenizer Tokenize(android::StringPiece str, char sep) {
return Tokenizer(str, sep);
}
@@ -263,7 +262,7 @@
// Extracts ".xml" into outSuffix.
//
// Returns true if successful.
-bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
+bool ExtractResFilePathParts(android::StringPiece path, android::StringPiece* out_prefix,
android::StringPiece* out_entry, android::StringPiece* out_suffix);
} // namespace util
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 4ebcb11..15135690 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -84,6 +84,14 @@
ASSERT_THAT(*iter, Eq(StringPiece()));
}
+TEST(UtilTest, TokenizeNone) {
+ auto tokenizer = util::Tokenize(StringPiece("none"), '.');
+ auto iter = tokenizer.begin();
+ ASSERT_THAT(*iter, Eq("none"));
+ ++iter;
+ ASSERT_THAT(iter, Eq(tokenizer.end()));
+}
+
TEST(UtilTest, IsJavaClassName) {
EXPECT_TRUE(util::IsJavaClassName("android.test.Class"));
EXPECT_TRUE(util::IsJavaClassName("android.test.Class$Inner"));
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 9bdbd22..3ccbaa2 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -84,7 +84,7 @@
error_msg << "unexpected element ";
PrintElementToDiagMessage(child_el, &error_msg);
error_msg << " found in ";
- for (const StringPiece& element : *bread_crumb) {
+ for (StringPiece element : *bread_crumb) {
error_msg << "<" << element << ">";
}
if (policy == XmlActionExecutorPolicy::kAllowListWarning) {
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index f51e8a4..8dea8ea 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -169,7 +169,7 @@
stack->last_text_node = util::make_unique<Text>();
stack->last_text_node->line_number = XML_GetCurrentLineNumber(parser);
stack->last_text_node->column_number = XML_GetCurrentColumnNumber(parser);
- stack->last_text_node->text = str.to_string();
+ stack->last_text_node->text.assign(str);
}
static void XMLCALL CommentDataHandler(void* user_data, const char* comment) {
@@ -417,11 +417,11 @@
children.insert(children.begin() + index, std::move(child));
}
-Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) {
return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
}
-const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
+const Attribute* Element::FindAttribute(StringPiece ns, StringPiece name) const {
for (const auto& attr : attributes) {
if (ns == attr.namespace_uri && name == attr.name) {
return &attr;
@@ -430,7 +430,7 @@
return nullptr;
}
-void Element::RemoveAttribute(const StringPiece& ns, const StringPiece& name) {
+void Element::RemoveAttribute(StringPiece ns, StringPiece name) {
auto new_attr_end = std::remove_if(attributes.begin(), attributes.end(),
[&](const Attribute& attr) -> bool {
return ns == attr.namespace_uri && name == attr.name;
@@ -439,34 +439,32 @@
attributes.erase(new_attr_end, attributes.end());
}
-Attribute* Element::FindOrCreateAttribute(const StringPiece& ns, const StringPiece& name) {
+Attribute* Element::FindOrCreateAttribute(StringPiece ns, StringPiece name) {
Attribute* attr = FindAttribute(ns, name);
if (attr == nullptr) {
- attributes.push_back(Attribute{ns.to_string(), name.to_string()});
+ attributes.push_back(Attribute{std::string(ns), std::string(name)});
attr = &attributes.back();
}
return attr;
}
-Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) {
+Element* Element::FindChild(StringPiece ns, StringPiece name) {
return FindChildWithAttribute(ns, name, {}, {}, {});
}
-const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+const Element* Element::FindChild(StringPiece ns, StringPiece name) const {
return FindChildWithAttribute(ns, name, {}, {}, {});
}
-Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
- const StringPiece& attr_ns, const StringPiece& attr_name,
- const StringPiece& attr_value) {
+Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name, StringPiece attr_ns,
+ StringPiece attr_name, StringPiece attr_value) {
return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
ns, name, attr_ns, attr_name, attr_value));
}
-const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
- const StringPiece& attr_ns,
- const StringPiece& attr_name,
- const StringPiece& attr_value) const {
+const Element* Element::FindChildWithAttribute(StringPiece ns, StringPiece name,
+ StringPiece attr_ns, StringPiece attr_name,
+ StringPiece attr_value) const {
for (const auto& child : children) {
if (const Element* el = NodeCast<Element>(child.get())) {
if (ns == el->namespace_uri && name == el->name) {
@@ -559,7 +557,7 @@
}
std::optional<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(
- const StringPiece& alias) const {
+ StringPiece alias) const {
if (alias.empty()) {
return ExtractedPackage{{}, false /*private*/};
}
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 5bc55b6..c253b0a 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -96,27 +96,22 @@
void AppendChild(std::unique_ptr<Node> child);
void InsertChild(size_t index, std::unique_ptr<Node> child);
- Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
- const Attribute* FindAttribute(const android::StringPiece& ns,
- const android::StringPiece& name) const;
- Attribute* FindOrCreateAttribute(const android::StringPiece& ns,
- const android::StringPiece& name);
- void RemoveAttribute(const android::StringPiece& ns,
- const android::StringPiece& name);
+ Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name);
+ const Attribute* FindAttribute(android::StringPiece ns, android::StringPiece name) const;
+ Attribute* FindOrCreateAttribute(android::StringPiece ns, android::StringPiece name);
+ void RemoveAttribute(android::StringPiece ns, android::StringPiece name);
- Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
- const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+ Element* FindChild(android::StringPiece ns, android::StringPiece name);
+ const Element* FindChild(android::StringPiece ns, android::StringPiece name) const;
- Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
- const android::StringPiece& attr_ns,
- const android::StringPiece& attr_name,
- const android::StringPiece& attr_value);
+ Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+ android::StringPiece attr_ns, android::StringPiece attr_name,
+ android::StringPiece attr_value);
- const Element* FindChildWithAttribute(const android::StringPiece& ns,
- const android::StringPiece& name,
- const android::StringPiece& attr_ns,
- const android::StringPiece& attr_name,
- const android::StringPiece& attr_value) const;
+ const Element* FindChildWithAttribute(android::StringPiece ns, android::StringPiece name,
+ android::StringPiece attr_ns,
+ android::StringPiece attr_name,
+ android::StringPiece attr_value) const;
std::vector<Element*> GetChildElements();
@@ -235,8 +230,7 @@
public:
using Visitor::Visit;
- std::optional<ExtractedPackage> TransformPackageAlias(
- const android::StringPiece& alias) const override;
+ std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
protected:
PackageAwareVisitor() = default;
diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index bfa0749..d79446b 100644
--- a/tools/aapt2/xml/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -140,8 +140,7 @@
return event_queue_.front().data2;
}
-std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(
- const StringPiece& alias) const {
+std::optional<ExtractedPackage> XmlPullParser::TransformPackageAlias(StringPiece alias) const {
if (alias.empty()) {
return ExtractedPackage{{}, false /*private*/};
}
@@ -307,7 +306,7 @@
parser->depth_ });
}
-std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, const StringPiece& name) {
+std::optional<StringPiece> FindAttribute(const XmlPullParser* parser, StringPiece name) {
auto iter = parser->FindAttribute("", name);
if (iter != parser->end_attributes()) {
return StringPiece(util::TrimWhitespace(iter->value));
@@ -315,8 +314,7 @@
return {};
}
-std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
- const StringPiece& name) {
+std::optional<StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser, StringPiece name) {
auto iter = parser->FindAttribute("", name);
if (iter != parser->end_attributes()) {
StringPiece trimmed = util::TrimWhitespace(iter->value);
diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index ab34772..fe4cd01 100644
--- a/tools/aapt2/xml/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -120,8 +120,7 @@
* If xmlns:app="http://schemas.android.com/apk/res-auto", then
* 'package' will be set to 'defaultPackage'.
*/
- std::optional<ExtractedPackage> TransformPackageAlias(
- const android::StringPiece& alias) const override;
+ std::optional<ExtractedPackage> TransformPackageAlias(android::StringPiece alias) const override;
struct PackageDecl {
std::string prefix;
@@ -194,7 +193,7 @@
* Finds the attribute in the current element within the global namespace.
*/
std::optional<android::StringPiece> FindAttribute(const XmlPullParser* parser,
- const android::StringPiece& name);
+ android::StringPiece name);
/**
* Finds the attribute in the current element within the global namespace. The
@@ -202,7 +201,7 @@
* must not be the empty string.
*/
std::optional<android::StringPiece> FindNonEmptyAttribute(const XmlPullParser* parser,
- const android::StringPiece& name);
+ android::StringPiece name);
//
// Implementation
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
index 114b5ba..709755e 100644
--- a/tools/aapt2/xml/XmlUtil.cpp
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -27,7 +27,7 @@
namespace aapt {
namespace xml {
-std::string BuildPackageNamespace(const StringPiece& package, bool private_reference) {
+std::string BuildPackageNamespace(StringPiece package, bool private_reference) {
std::string result = private_reference ? kSchemaPrivatePrefix : kSchemaPublicPrefix;
result.append(package.data(), package.size());
return result;
@@ -41,7 +41,7 @@
if (package.empty()) {
return {};
}
- return ExtractedPackage{package.to_string(), false /* is_private */};
+ return ExtractedPackage{std::string(package), false /* is_private */};
} else if (util::StartsWith(namespace_uri, kSchemaPrivatePrefix)) {
StringPiece schema_prefix = kSchemaPrivatePrefix;
@@ -50,7 +50,7 @@
if (package.empty()) {
return {};
}
- return ExtractedPackage{package.to_string(), true /* is_private */};
+ return ExtractedPackage{std::string(package), true /* is_private */};
} else if (namespace_uri == kSchemaAuto) {
return ExtractedPackage{std::string(), true /* is_private */};
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
index 1ab05a9..ad676ca 100644
--- a/tools/aapt2/xml/XmlUtil.h
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -59,8 +59,7 @@
//
// If privateReference == true, the package will be of the form:
// http://schemas.android.com/apk/prv/res/<package>
-std::string BuildPackageNamespace(const android::StringPiece& package,
- bool private_reference = false);
+std::string BuildPackageNamespace(android::StringPiece package, bool private_reference = false);
// Interface representing a stack of XML namespace declarations. When looking up the package for a
// namespace prefix, the stack is checked from top to bottom.
@@ -69,7 +68,7 @@
// Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
virtual std::optional<ExtractedPackage> TransformPackageAlias(
- const android::StringPiece& alias) const = 0;
+ android::StringPiece alias) const = 0;
};
// Helper function for transforming the original Reference inRef to a fully qualified reference
diff --git a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
index f29d9b2..c6f6d45 100644
--- a/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
+++ b/tools/processors/immutability/src/android/processor/immutability/ImmutabilityProcessor.kt
@@ -54,6 +54,7 @@
"java.lang.Short",
"java.lang.String",
"java.lang.Void",
+ "java.util.UUID",
"android.os.Parcelable.Creator",
)